Comparing version 4.0.1 to 4.1.0
@@ -24,7 +24,8 @@ import { EventEmitter } from 'events'; | ||
} | ||
declare enum Versions { | ||
REST_VERSION = 4, | ||
WEBSOCKET_VERSION = 4 | ||
} | ||
declare const Versions: { | ||
REST_VERSION: number; | ||
WEBSOCKET_VERSION: number; | ||
}; | ||
declare const ShoukakuDefaults: Required<ShoukakuOptions>; | ||
declare const ShoukakuClientInfo: string; | ||
declare const NodeDefaults: NodeOption; | ||
@@ -35,6 +36,6 @@ | ||
declare const Constants_OpCodes: typeof OpCodes; | ||
declare const Constants_ShoukakuClientInfo: typeof ShoukakuClientInfo; | ||
declare const Constants_ShoukakuDefaults: typeof ShoukakuDefaults; | ||
type Constants_State = State; | ||
declare const Constants_State: typeof State; | ||
type Constants_Versions = Versions; | ||
declare const Constants_Versions: typeof Versions; | ||
@@ -44,6 +45,37 @@ type Constants_VoiceState = VoiceState; | ||
declare namespace Constants { | ||
export { Constants_NodeDefaults as NodeDefaults, Constants_OpCodes as OpCodes, Constants_ShoukakuDefaults as ShoukakuDefaults, Constants_State as State, Constants_Versions as Versions, Constants_VoiceState as VoiceState }; | ||
export { Constants_NodeDefaults as NodeDefaults, Constants_OpCodes as OpCodes, Constants_ShoukakuClientInfo as ShoukakuClientInfo, Constants_ShoukakuDefaults as ShoukakuDefaults, Constants_State as State, Constants_Versions as Versions, Constants_VoiceState as VoiceState }; | ||
} | ||
declare abstract class TypedEventEmitter<T extends Record<string, unknown[]>> extends EventEmitter { | ||
protected constructor(); | ||
on<K extends Extract<keyof T, string> | symbol>(eventName: K, listener: (...args: T[Extract<K, string>]) => void): this; | ||
once<K extends Extract<keyof T, string> | symbol>(eventName: K, listener: (...args: T[Extract<K, string>]) => void): this; | ||
off<K extends Extract<keyof T, string> | symbol>(eventName: K, listener: (...args: T[Extract<K, string>]) => void): this; | ||
emit<K extends Extract<keyof T, string> | symbol>(eventName: K, ...args: T[Extract<K, string>]): boolean; | ||
} | ||
type Constructor<T> = new (...args: unknown[]) => T; | ||
/** | ||
* Merge the default options to user input | ||
* @param def Default options | ||
* @param given User input | ||
* @returns Merged options | ||
*/ | ||
declare function mergeDefault<T extends Record<string, any>>(def: T, given: T): Required<T>; | ||
/** | ||
* Wait for a specific amount of time (timeout) | ||
* @param ms Time to wait in milliseconds | ||
* @returns A promise that resolves in x seconds | ||
*/ | ||
declare function wait(ms: number): Promise<void>; | ||
type Utils_Constructor<T> = Constructor<T>; | ||
type Utils_TypedEventEmitter<T extends Record<string, unknown[]>> = TypedEventEmitter<T>; | ||
declare const Utils_TypedEventEmitter: typeof TypedEventEmitter; | ||
declare const Utils_mergeDefault: typeof mergeDefault; | ||
declare const Utils_wait: typeof wait; | ||
declare namespace Utils { | ||
export { type Utils_Constructor as Constructor, Utils_TypedEventEmitter as TypedEventEmitter, Utils_mergeDefault as mergeDefault, Utils_wait as wait }; | ||
} | ||
/** | ||
* Represents the partial payload from a stateUpdate event | ||
@@ -74,11 +106,11 @@ */ | ||
/** | ||
* ID of Guild that contains the connected voice channel | ||
* GuildId of the connection that is being managed by this instance | ||
*/ | ||
guildId: string; | ||
/** | ||
* ID of the connected voice channel | ||
* VoiceChannelId of the connection that is being managed by this instance | ||
*/ | ||
channelId: string | null; | ||
/** | ||
* ID of the Shard that contains the guild that contains the connected voice channel | ||
* ShardId where this connection sends data on | ||
*/ | ||
@@ -95,7 +127,7 @@ shardId: number; | ||
/** | ||
* ID of the last channelId connected to | ||
* Id of the voice channel where this instance was connected before the current channelId | ||
*/ | ||
lastChannelId: string | null; | ||
/** | ||
* ID of current session | ||
* Id of the currently active voice channel connection | ||
*/ | ||
@@ -127,3 +159,2 @@ sessionId: string | null; | ||
* @param options.mute Optional boolean value to specify whether to mute the current bot user | ||
* @param options.getNode Optional move function for moving players around | ||
*/ | ||
@@ -154,6 +185,6 @@ constructor(manager: Shoukaku, options: VoiceChannelOptions); | ||
/** | ||
* Update Session ID, Channel ID, Deafen status and Mute status of this instance | ||
* Updates SessionId, ChannelId, Deafen and Mute data of this instance | ||
* | ||
* @param options.session_id ID of this session | ||
* @param options.channel_id ID of currently connected voice channel | ||
* @param options.session_id Id of the current session | ||
* @param options.channel_id Id of the connected voice channel | ||
* @param options.self_deaf Boolean that indicates if the current bot user is deafened or not | ||
@@ -188,22 +219,11 @@ * @param options.self_mute Boolean that indicates if the current bot user is muted or not | ||
type TrackEndReason = 'finished' | 'loadFailed' | 'stopped' | 'replaced' | 'cleanup'; | ||
type PlayerEventType = 'TrackStartEvent' | 'TrackEndEvent' | 'TrackExceptionEvent' | 'TrackStuckEvent' | 'WebSocketClosedEvent'; | ||
/** | ||
* Options when playing a new track | ||
*/ | ||
interface PlayOptions { | ||
track: string; | ||
options?: { | ||
noReplace?: boolean; | ||
pause?: boolean; | ||
startTime?: number; | ||
endTime?: number; | ||
volume?: number; | ||
}; | ||
type PlayOptions = Omit<UpdatePlayerOptions, 'filters' | 'voice'>; | ||
type ResumeOptions = Omit<UpdatePlayerOptions, 'track' | 'filters' | 'voice'>; | ||
declare enum PlayerEventType { | ||
TRACK_START_EVENT = "TrackStartEvent", | ||
TRACK_END_EVENT = "TrackEndEvent", | ||
TRACK_EXCEPTION_EVENT = "TrackExceptionEvent", | ||
TRACK_STUCK_EVENT = "TrackStuckEvent", | ||
WEBSOCKET_CLOSED_EVENT = "WebSocketClosedEvent" | ||
} | ||
interface ResumeOptions { | ||
noReplace?: boolean; | ||
pause?: boolean; | ||
startTime?: number; | ||
endTime?: number; | ||
} | ||
interface Band { | ||
@@ -252,29 +272,24 @@ band: number; | ||
op: OpCodes.EVENT; | ||
type: PlayerEventType; | ||
guildId: string; | ||
} | ||
interface TrackStartEvent extends PlayerEvent { | ||
type: 'TrackStartEvent'; | ||
type: PlayerEventType.TRACK_START_EVENT; | ||
track: Track; | ||
} | ||
interface TrackEndEvent extends PlayerEvent { | ||
type: 'TrackEndEvent'; | ||
type: PlayerEventType.TRACK_END_EVENT; | ||
track: Track; | ||
reason: TrackEndReason; | ||
} | ||
interface TrackExceptionEvent extends PlayerEvent { | ||
type: 'TrackExceptionEvent'; | ||
exception: Exception; | ||
} | ||
interface TrackStuckEvent extends PlayerEvent { | ||
type: 'TrackStuckEvent'; | ||
type: PlayerEventType.TRACK_STUCK_EVENT; | ||
track: Track; | ||
thresholdMs: number; | ||
} | ||
interface TrackStuckEvent extends PlayerEvent { | ||
type: 'TrackStuckEvent'; | ||
thresholdMs: number; | ||
interface TrackExceptionEvent extends PlayerEvent { | ||
type: PlayerEventType.TRACK_EXCEPTION_EVENT; | ||
exception: Exception; | ||
} | ||
interface WebSocketClosedEvent extends PlayerEvent { | ||
type: 'WebSocketClosedEvent'; | ||
type: PlayerEventType.WEBSOCKET_CLOSED_EVENT; | ||
code: number; | ||
@@ -288,4 +303,5 @@ byRemote: boolean; | ||
connected: boolean; | ||
position?: number; | ||
position: number; | ||
time: number; | ||
ping: number; | ||
}; | ||
@@ -306,3 +322,3 @@ guildId: string; | ||
} | ||
declare interface Player { | ||
type PlayerEvents = { | ||
/** | ||
@@ -312,3 +328,3 @@ * Emitted when the current playing track ends | ||
*/ | ||
on(event: 'end', listener: (reason: TrackEndEvent) => void): this; | ||
'end': [reason: TrackEndEvent]; | ||
/** | ||
@@ -318,3 +334,3 @@ * Emitted when the current playing track gets stuck due to an error | ||
*/ | ||
on(event: 'stuck', listener: (data: TrackStuckEvent) => void): this; | ||
'stuck': [data: TrackStuckEvent]; | ||
/** | ||
@@ -324,3 +340,3 @@ * Emitted when the current websocket connection is closed | ||
*/ | ||
on(event: 'closed', listener: (reason: WebSocketClosedEvent) => void): this; | ||
'closed': [reason: WebSocketClosedEvent]; | ||
/** | ||
@@ -330,3 +346,3 @@ * Emitted when a new track starts | ||
*/ | ||
on(event: 'start', listener: (data: TrackStartEvent) => void): this; | ||
'start': [data: TrackStartEvent]; | ||
/** | ||
@@ -336,3 +352,3 @@ * Emitted when there is an error caused by the current playing track | ||
*/ | ||
on(event: 'exception', listener: (reason: TrackExceptionEvent) => void): this; | ||
'exception': [reason: TrackExceptionEvent]; | ||
/** | ||
@@ -342,3 +358,3 @@ * Emitted when the library manages to resume the player | ||
*/ | ||
on(event: 'resumed', listener: (player: Player) => void): this; | ||
'resumed': [player: Player]; | ||
/** | ||
@@ -348,22 +364,8 @@ * Emitted when a playerUpdate even is received from Lavalink | ||
*/ | ||
on(event: 'update', listener: (data: PlayerUpdate) => void): this; | ||
once(event: 'end', listener: (reason: TrackEndEvent) => void): this; | ||
once(event: 'stuck', listener: (data: TrackStuckEvent) => void): this; | ||
once(event: 'closed', listener: (reason: WebSocketClosedEvent) => void): this; | ||
once(event: 'start', listener: (data: TrackStartEvent) => void): this; | ||
once(event: 'exception', listener: (reason: TrackExceptionEvent) => void): this; | ||
once(event: 'resumed', listener: (player: Player) => void): this; | ||
once(event: 'update', listener: (data: PlayerUpdate) => void): this; | ||
off(event: 'end', listener: (reason: TrackEndEvent) => void): this; | ||
off(event: 'stuck', listener: (data: TrackStuckEvent) => void): this; | ||
off(event: 'closed', listener: (reason: WebSocketClosedEvent) => void): this; | ||
off(event: 'start', listener: (data: TrackStartEvent) => void): this; | ||
off(event: 'exception', listener: (reason: TrackExceptionEvent) => void): this; | ||
off(event: 'resumed', listener: (player: Player) => void): this; | ||
off(event: 'update', listener: (data: PlayerUpdate) => void): this; | ||
} | ||
'update': [data: PlayerUpdate]; | ||
}; | ||
/** | ||
* Wrapper object around Lavalink | ||
*/ | ||
declare class Player extends EventEmitter { | ||
declare class Player extends TypedEventEmitter<PlayerEvents> { | ||
/** | ||
@@ -378,3 +380,3 @@ * GuildId of this player | ||
/** | ||
* ID of current track | ||
* Base64 encoded data of the current track | ||
*/ | ||
@@ -410,3 +412,3 @@ track: string | null; | ||
* Move player to another node | ||
* @param name? Name of node to move to, or the default ideal node | ||
* @param name Name of node to move to, or the default ideal node | ||
* @returns true if the player was moved, false if not | ||
@@ -422,4 +424,5 @@ */ | ||
* @param playable Options for playing this track | ||
* @param noReplace Set it to true if you don't want to replace the currently playing track | ||
*/ | ||
playTrack(playable: PlayOptions): Promise<void>; | ||
playTrack(playerOptions: PlayOptions, noReplace?: boolean): Promise<void>; | ||
/** | ||
@@ -484,3 +487,3 @@ * Stop the currently playing track | ||
*/ | ||
setDistortion(distortion: DistortionSettings): Promise<void>; | ||
setDistortion(distortion?: DistortionSettings): Promise<void>; | ||
/** | ||
@@ -490,3 +493,3 @@ * Change the channel mix settings applied to the currently playing track | ||
*/ | ||
setChannelMix(channelMix: ChannelMixSettings): Promise<void>; | ||
setChannelMix(channelMix?: ChannelMixSettings): Promise<void>; | ||
/** | ||
@@ -496,3 +499,3 @@ * Change the low pass settings applied to the currently playing track | ||
*/ | ||
setLowPass(lowPass: LowPassSettings): Promise<void>; | ||
setLowPass(lowPass?: LowPassSettings): Promise<void>; | ||
/** | ||
@@ -510,8 +513,11 @@ * Change the all filter settings applied to the currently playing track | ||
* @param options An object that conforms to ResumeOptions that specify behavior on resuming | ||
* @param noReplace Set it to true if you don't want to replace the currently playing track | ||
*/ | ||
resume(options?: ResumeOptions): Promise<void>; | ||
resume(options?: ResumeOptions, noReplace?: boolean): Promise<void>; | ||
/** | ||
* If you want to update the whole player yourself, sends raw update player info to lavalink | ||
* @param playerOptions Options to update the player data | ||
* @param noReplace Set it to true if you don't want to replace the currently playing track | ||
*/ | ||
update(updatePlayer: UpdatePlayerInfo): Promise<void>; | ||
update(playerOptions: UpdatePlayerOptions, noReplace?: boolean): Promise<void>; | ||
/** | ||
@@ -530,8 +536,3 @@ * Cleans this player instance | ||
*/ | ||
onPlayerUpdate(json: { | ||
state: { | ||
position: number; | ||
ping: number; | ||
}; | ||
}): void; | ||
onPlayerUpdate(json: PlayerUpdate): void; | ||
/** | ||
@@ -542,6 +543,3 @@ * Handle player events received from Lavalink | ||
*/ | ||
onPlayerEvent(json: { | ||
type: string; | ||
track: Track; | ||
}): void; | ||
onPlayerEvent(json: TrackStartEvent | TrackEndEvent | TrackStuckEvent | TrackExceptionEvent | WebSocketClosedEvent): void; | ||
} | ||
@@ -602,3 +600,3 @@ | ||
loadType: LoadType.EMPTY; | ||
data: {}; | ||
data: Record<string, never>; | ||
} | ||
@@ -637,4 +635,3 @@ interface ErrorResult { | ||
} | ||
interface LavalinkPlayerVoiceOptions extends Omit<LavalinkPlayerVoice, 'connected' | 'ping'> { | ||
} | ||
type LavalinkPlayerVoiceOptions = Omit<LavalinkPlayerVoice, 'connected' | 'ping'>; | ||
interface LavalinkPlayer { | ||
@@ -648,5 +645,9 @@ guildId: string; | ||
} | ||
interface UpdatePlayerTrackOptions { | ||
encoded?: string | null; | ||
identifier?: string; | ||
userData?: unknown; | ||
} | ||
interface UpdatePlayerOptions { | ||
encodedTrack?: string | null; | ||
identifier?: string; | ||
track?: UpdatePlayerTrackOptions; | ||
position?: number; | ||
@@ -695,6 +696,2 @@ endTime?: number; | ||
/** | ||
* Rest version to use | ||
*/ | ||
protected readonly version: string; | ||
/** | ||
* @param node An instance of Node | ||
@@ -728,4 +725,4 @@ * @param options The options to initialize this rest class | ||
/** | ||
* Gets all the player with the specified sessionId | ||
* @returns Promise that resolves to an array of Lavalink players | ||
* Gets the player with the specified guildId | ||
* @returns Promise that resolves to a Lavalink player | ||
*/ | ||
@@ -755,3 +752,3 @@ getPlayer(guildId: string): Promise<LavalinkPlayer | undefined>; | ||
*/ | ||
stats(): Promise<NodeStats | undefined>; | ||
stats(): Promise<Stats | undefined>; | ||
/** | ||
@@ -775,2 +772,3 @@ * Get routeplanner status from Lavalink | ||
* @param fetchOptions.options Options passed to fetch | ||
* @throws `RestError` when encountering a Lavalink error response | ||
* @internal | ||
@@ -780,4 +778,26 @@ */ | ||
} | ||
interface LavalinkRestError { | ||
timestamp: number; | ||
status: number; | ||
error: string; | ||
trace?: string; | ||
message: string; | ||
path: string; | ||
} | ||
declare class RestError extends Error { | ||
timestamp: number; | ||
status: number; | ||
error: string; | ||
trace?: string; | ||
path: string; | ||
constructor({ timestamp, status, error, trace, message, path }: LavalinkRestError); | ||
} | ||
interface NodeStats { | ||
interface Ready { | ||
op: OpCodes.READY; | ||
resumed: boolean; | ||
sessionId: string; | ||
} | ||
interface Stats { | ||
op: OpCodes.STATS; | ||
players: number; | ||
@@ -791,3 +811,3 @@ playingPlayers: number; | ||
}; | ||
frameStats: { | ||
frameStats?: { | ||
sent: number; | ||
@@ -804,3 +824,3 @@ deficit: number; | ||
} | ||
type NodeInfoVersion = { | ||
interface NodeInfoVersion { | ||
semver: string; | ||
@@ -812,13 +832,13 @@ major: number; | ||
build?: string; | ||
}; | ||
type NodeInfoGit = { | ||
} | ||
interface NodeInfoGit { | ||
branch: string; | ||
commit: string; | ||
commitTime: number; | ||
}; | ||
type NodeInfoPlugin = { | ||
} | ||
interface NodeInfoPlugin { | ||
name: string; | ||
version: string; | ||
}; | ||
type NodeInfo = { | ||
} | ||
interface NodeInfo { | ||
version: NodeInfoVersion; | ||
@@ -832,3 +852,3 @@ buildTime: number; | ||
plugins: NodeInfoPlugin[]; | ||
}; | ||
} | ||
interface ResumableHeaders { | ||
@@ -842,8 +862,10 @@ [key: string]: string; | ||
} | ||
interface NonResumableHeaders extends Omit<ResumableHeaders, 'Session-Id'> { | ||
} | ||
type NonResumableHeaders = Omit<ResumableHeaders, 'Session-Id'>; | ||
type NodeEvents = { | ||
[K in keyof ShoukakuEvents]: ShoukakuEvents[K] extends [unknown, ...infer R] ? R : never; | ||
}; | ||
/** | ||
* Represents a Lavalink node | ||
*/ | ||
declare class Node extends EventEmitter { | ||
declare class Node extends TypedEventEmitter<NodeEvents> { | ||
/** | ||
@@ -866,6 +888,2 @@ * Shoukaku class | ||
/** | ||
* Websocket version this node will use | ||
*/ | ||
readonly version: string; | ||
/** | ||
* URL of Lavalink | ||
@@ -889,3 +907,3 @@ */ | ||
*/ | ||
stats: NodeStats | null; | ||
stats: Stats | null; | ||
/** | ||
@@ -937,3 +955,3 @@ * Information about lavalink node | ||
/** | ||
* Disconnect from lavalink | ||
* Disconnect from Lavalink | ||
* @param code Status code | ||
@@ -965,4 +983,9 @@ * @param reason Reason for disconnect | ||
*/ | ||
error(error: Error | unknown): void; | ||
error(error: Error): void; | ||
/** | ||
* Internal disconnect function | ||
* @internal | ||
*/ | ||
private internalDisconnect; | ||
/** | ||
* Destroys the websocket connection | ||
@@ -994,26 +1017,2 @@ * @internal | ||
type Constructor<T> = new (...args: any[]) => T; | ||
/** | ||
* Merge the default options to user input | ||
* @param def Default options | ||
* @param given User input | ||
* @returns Merged options | ||
*/ | ||
declare function mergeDefault<T extends { | ||
[key: string]: any; | ||
}>(def: T, given: T): Required<T>; | ||
/** | ||
* Wait for a specific amount of time (timeout) | ||
* @param ms Time to wait in milliseconds | ||
* @returns A promise that resolves in x seconds | ||
*/ | ||
declare function wait(ms: number): Promise<void>; | ||
type Utils_Constructor<T> = Constructor<T>; | ||
declare const Utils_mergeDefault: typeof mergeDefault; | ||
declare const Utils_wait: typeof wait; | ||
declare namespace Utils { | ||
export { type Utils_Constructor as Constructor, Utils_mergeDefault as mergeDefault, Utils_wait as wait }; | ||
} | ||
interface Structures { | ||
@@ -1031,7 +1030,7 @@ /** | ||
/** | ||
* Name of this node | ||
* Name of the Lavalink node | ||
*/ | ||
name: string; | ||
/** | ||
* URL of Lavalink | ||
* Lavalink node host and port without any prefix | ||
*/ | ||
@@ -1048,3 +1047,3 @@ url: string; | ||
/** | ||
* Group of this node | ||
* Name of the Lavalink node group | ||
*/ | ||
@@ -1106,3 +1105,3 @@ group?: string; | ||
} | ||
declare interface Shoukaku { | ||
type ShoukakuEvents = { | ||
/** | ||
@@ -1112,3 +1111,3 @@ * Emitted when reconnect tries are occurring and how many tries are left | ||
*/ | ||
on(event: 'reconnecting', listener: (name: string, reconnectsLeft: number, reconnectInterval: number) => void): this; | ||
'reconnecting': [name: string, reconnectsLeft: number, reconnectInterval: number]; | ||
/** | ||
@@ -1118,3 +1117,3 @@ * Emitted when data useful for debugging is produced | ||
*/ | ||
on(event: 'debug', listener: (name: string, info: string) => void): this; | ||
'debug': [name: string, info: string]; | ||
/** | ||
@@ -1124,3 +1123,3 @@ * Emitted when an error occurs | ||
*/ | ||
on(event: 'error', listener: (name: string, error: Error) => void): this; | ||
'error': [name: string, error: Error]; | ||
/** | ||
@@ -1130,3 +1129,3 @@ * Emitted when Shoukaku is ready to receive operations | ||
*/ | ||
on(event: 'ready', listener: (name: string, reconnected: boolean) => void): this; | ||
'ready': [name: string, lavalinkResume: boolean, libraryResume: boolean]; | ||
/** | ||
@@ -1136,3 +1135,3 @@ * Emitted when a websocket connection to Lavalink closes | ||
*/ | ||
on(event: 'close', listener: (name: string, code: number, reason: string) => void): this; | ||
'close': [name: string, code: number, reason: string]; | ||
/** | ||
@@ -1142,3 +1141,3 @@ * Emitted when a websocket connection to Lavalink disconnects | ||
*/ | ||
on(event: 'disconnect', listener: (name: string, count: number) => void): this; | ||
'disconnect': [name: string, count: number]; | ||
/** | ||
@@ -1148,22 +1147,8 @@ * Emitted when a raw message is received from Lavalink | ||
*/ | ||
on(event: 'raw', listener: (name: string, json: unknown) => void): this; | ||
once(event: 'reconnecting', listener: (name: string, reconnectsLeft: number, reconnectInterval: number) => void): this; | ||
once(event: 'debug', listener: (name: string, info: string) => void): this; | ||
once(event: 'error', listener: (name: string, error: Error) => void): this; | ||
once(event: 'ready', listener: (name: string, reconnected: boolean) => void): this; | ||
once(event: 'close', listener: (name: string, code: number, reason: string) => void): this; | ||
once(event: 'disconnect', listener: (name: string, count: number) => void): this; | ||
once(event: 'raw', listener: (name: string, json: unknown) => void): this; | ||
off(event: 'reconnecting', listener: (name: string, reconnectsLeft: number, reconnectInterval: number) => void): this; | ||
off(event: 'debug', listener: (name: string, info: string) => void): this; | ||
off(event: 'error', listener: (name: string, error: Error) => void): this; | ||
off(event: 'ready', listener: (name: string, reconnected: boolean) => void): this; | ||
off(event: 'close', listener: (name: string, code: number, reason: string) => void): this; | ||
off(event: 'disconnect', listener: (name: string, count: number) => void): this; | ||
off(event: 'raw', listener: (name: string, json: unknown) => void): this; | ||
} | ||
'raw': [name: string, json: unknown]; | ||
}; | ||
/** | ||
* Main Shoukaku class | ||
*/ | ||
declare class Shoukaku extends EventEmitter { | ||
declare class Shoukaku extends TypedEventEmitter<ShoukakuEvents> { | ||
/** | ||
@@ -1210,2 +1195,8 @@ * Discord library connector | ||
/** | ||
* Gets an ideal node based on the nodeResolver you provided | ||
* @param connection Optional connection class for ideal node selection, if you use it | ||
* @returns An ideal node for you to do things with | ||
*/ | ||
getIdealNode(connection?: Connection): Node | undefined; | ||
/** | ||
* Add a Lavalink node to the pool of available nodes | ||
@@ -1233,3 +1224,2 @@ * @param options.name Name of this node | ||
* @returns The created player | ||
* @internal | ||
*/ | ||
@@ -1241,3 +1231,2 @@ joinVoiceChannel(options: VoiceChannelOptions): Promise<Player>; | ||
* @returns The destroyed / disconnected player or undefined if none | ||
* @internal | ||
*/ | ||
@@ -1268,3 +1257,3 @@ leaveVoiceChannel(guildId: string): Promise<void>; | ||
abstract getId(): string; | ||
abstract sendPacket(shardId: number, payload: any, important: boolean): void; | ||
abstract sendPacket(shardId: number, payload: unknown, important: boolean): void; | ||
abstract listen(nodes: NodeOption[]): void; | ||
@@ -1291,2 +1280,8 @@ } | ||
declare class Seyfert extends Connector { | ||
sendPacket(shardId: number, payload: unknown, important: boolean): void; | ||
getId(): string; | ||
listen(nodes: NodeOption[]): void; | ||
} | ||
type index_DiscordJS = DiscordJS; | ||
@@ -1298,6 +1293,8 @@ declare const index_DiscordJS: typeof DiscordJS; | ||
declare const index_OceanicJS: typeof OceanicJS; | ||
type index_Seyfert = Seyfert; | ||
declare const index_Seyfert: typeof Seyfert; | ||
declare namespace index { | ||
export { index_DiscordJS as DiscordJS, index_Eris as Eris, index_OceanicJS as OceanicJS }; | ||
export { index_DiscordJS as DiscordJS, index_Eris as Eris, index_OceanicJS as OceanicJS, index_Seyfert as Seyfert }; | ||
} | ||
export { type Address, AllowedPackets, type Band, type ChannelMixSettings, Connection, Connector, type ConnectorMethods, index as Connectors, Constants, type DistortionSettings, type EmptyResult, type ErrorResult, type Exception, type FilterOptions, type FreqSettings, type KaraokeSettings, type LavalinkPlayer, type LavalinkPlayerVoice, type LavalinkPlayerVoiceOptions, type LavalinkResponse, LoadType, type LowPassSettings, Node, type NodeInfo, type NodeOption, type NodeStats, type NonResumableHeaders, type PlayOptions, Player, type PlayerEvent, type PlayerEventType, type PlayerUpdate, type Playlist, type PlaylistResult, Rest, type ResumableHeaders, type ResumeOptions, type RotationSettings, type RoutePlanner, type SearchResult, type ServerUpdate, type SessionInfo, type Severity, Shoukaku, type ShoukakuOptions, type StateUpdatePartial, type Structures, type TimescaleSettings, type Track, type TrackEndEvent, type TrackEndReason, type TrackExceptionEvent, type TrackResult, type TrackStartEvent, type TrackStuckEvent, type UpdatePlayerInfo, type UpdatePlayerOptions, Utils, type VoiceChannelOptions, type WebSocketClosedEvent }; | ||
export { type Address, AllowedPackets, type Band, type ChannelMixSettings, Connection, Connector, type ConnectorMethods, index as Connectors, Constants, type DistortionSettings, type EmptyResult, type ErrorResult, type Exception, type FilterOptions, type FreqSettings, type KaraokeSettings, type LavalinkPlayer, type LavalinkPlayerVoice, type LavalinkPlayerVoiceOptions, type LavalinkResponse, LoadType, type LowPassSettings, Node, type NodeEvents, type NodeInfo, type NodeInfoGit, type NodeInfoPlugin, type NodeInfoVersion, type NodeOption, type NonResumableHeaders, type PlayOptions, Player, type PlayerEvent, PlayerEventType, type PlayerEvents, type PlayerUpdate, type Playlist, type PlaylistResult, type Ready, Rest, RestError, type ResumableHeaders, type ResumeOptions, type RotationSettings, type RoutePlanner, type SearchResult, type ServerUpdate, type SessionInfo, type Severity, Shoukaku, type ShoukakuEvents, type ShoukakuOptions, type StateUpdatePartial, type Stats, type Structures, type TimescaleSettings, type Track, type TrackEndEvent, type TrackEndReason, type TrackExceptionEvent, type TrackResult, type TrackStartEvent, type TrackStuckEvent, type UpdatePlayerInfo, type UpdatePlayerOptions, type UpdatePlayerTrackOptions, Utils, type VoiceChannelOptions, type WebSocketClosedEvent }; |
1045
dist/index.js
@@ -41,3 +41,5 @@ "use strict"; | ||
Player: () => Player, | ||
PlayerEventType: () => PlayerEventType, | ||
Rest: () => Rest, | ||
RestError: () => RestError, | ||
Shoukaku: () => Shoukaku, | ||
@@ -53,3 +55,4 @@ Utils: () => Utils_exports | ||
Eris: () => Eris, | ||
OceanicJS: () => OceanicJS | ||
OceanicJS: () => OceanicJS, | ||
Seyfert: () => Seyfert | ||
}); | ||
@@ -62,2 +65,3 @@ | ||
OpCodes: () => OpCodes, | ||
ShoukakuClientInfo: () => ShoukakuClientInfo, | ||
ShoukakuDefaults: () => ShoukakuDefaults, | ||
@@ -72,3 +76,3 @@ State: () => State, | ||
name: "shoukaku", | ||
version: "4.0.1", | ||
version: "4.1.0", | ||
description: "A stable and updated wrapper around Lavalink", | ||
@@ -89,3 +93,3 @@ main: "dist/index.js", | ||
"build:docs": "typedoc --theme default --readme README.md --out docs/ --entryPointStrategy expand src/.", | ||
lint: "eslint --fix --ext .ts", | ||
lint: "eslint .", | ||
prepare: "npm run build:ts" | ||
@@ -115,15 +119,12 @@ }, | ||
dependencies: { | ||
ws: "^8.14.2" | ||
ws: "^8.18.0" | ||
}, | ||
devDependencies: { | ||
"@augu/eslint-config": "^5.0.0", | ||
"@types/node": "^20.10.5", | ||
"@types/node-fetch": "^2.6.9", | ||
"@types/ws": "^8.5.10", | ||
"@typescript-eslint/eslint-plugin": "^6.15.0", | ||
"@typescript-eslint/parser": "^6.15.0", | ||
eslint: "^8.56.0", | ||
tsup: "^8.0.1", | ||
typedoc: "^0.25.4", | ||
typescript: "^5.3.3" | ||
"@shipgirl/eslint-config": "^0.2.2", | ||
"@types/node": "^22.2.0", | ||
"@types/ws": "^8.5.12", | ||
eslint: "^9.9.0", | ||
tsup: "^8.2.4", | ||
typedoc: "^0.26.5", | ||
typescript: "^5.5.4" | ||
} | ||
@@ -156,7 +157,6 @@ }; | ||
})(OpCodes || {}); | ||
var Versions = /* @__PURE__ */ ((Versions2) => { | ||
Versions2[Versions2["REST_VERSION"] = 4] = "REST_VERSION"; | ||
Versions2[Versions2["WEBSOCKET_VERSION"] = 4] = "WEBSOCKET_VERSION"; | ||
return Versions2; | ||
})(Versions || {}); | ||
var Versions = { | ||
REST_VERSION: 4, | ||
WEBSOCKET_VERSION: 4 | ||
}; | ||
var ShoukakuDefaults = { | ||
@@ -170,3 +170,3 @@ resume: false, | ||
moveOnDisconnect: false, | ||
userAgent: `${package_default.name}bot/${package_default.version} (${package_default.repository.url})`, | ||
userAgent: "Discord Bot/unknown (https://github.com/shipgirlproject/Shoukaku.git)", | ||
structures: {}, | ||
@@ -176,2 +176,3 @@ voiceConnectionTimeout: 15, | ||
}; | ||
var ShoukakuClientInfo = `${package_default.name}/${package_default.version} (${package_default.repository.url})`; | ||
var NodeDefaults = { | ||
@@ -188,12 +189,29 @@ name: "Default", | ||
__export(Utils_exports, { | ||
TypedEventEmitter: () => TypedEventEmitter, | ||
mergeDefault: () => mergeDefault, | ||
wait: () => wait | ||
}); | ||
var import_events = require("events"); | ||
var TypedEventEmitter = class extends import_events.EventEmitter { | ||
constructor() { | ||
super(); | ||
} | ||
on(eventName, listener) { | ||
return super.on(eventName, listener); | ||
} | ||
once(eventName, listener) { | ||
return super.once(eventName, listener); | ||
} | ||
off(eventName, listener) { | ||
return super.off(eventName, listener); | ||
} | ||
emit(eventName, ...args) { | ||
return super.emit(eventName, ...args); | ||
} | ||
}; | ||
function mergeDefault(def, given) { | ||
if (!given) | ||
return def; | ||
if (!given) return def; | ||
const defaultKeys = Object.keys(def); | ||
for (const key in given) { | ||
if (defaultKeys.includes(key)) | ||
continue; | ||
if (defaultKeys.includes(key)) continue; | ||
delete given[key]; | ||
@@ -203,7 +221,5 @@ } | ||
if (def[key] === null || typeof def[key] === "string" && def[key].length === 0) { | ||
if (!given[key]) | ||
throw new Error(`${String(key)} was not found from the given options.`); | ||
if (!given[key]) throw new Error(`${String(key)} was not found from the given options.`); | ||
} | ||
if (given[key] === null || given[key] === void 0) | ||
given[key] = def[key]; | ||
if (given[key] === null || given[key] === void 0) given[key] = def[key]; | ||
} | ||
@@ -229,17 +245,12 @@ return given; | ||
this.manager.id = this.getId(); | ||
for (const node of nodes) | ||
this.manager.addNode(mergeDefault(NodeDefaults, node)); | ||
for (const node of nodes) this.manager.addNode(mergeDefault(NodeDefaults, node)); | ||
} | ||
raw(packet) { | ||
if (!AllowedPackets.includes(packet.t)) | ||
return; | ||
if (!AllowedPackets.includes(packet.t)) return; | ||
const guildId = packet.d.guild_id; | ||
const connection = this.manager.connections.get(guildId); | ||
if (!connection) | ||
return; | ||
if (packet.t === "VOICE_SERVER_UPDATE") | ||
return connection.setServerUpdate(packet.d); | ||
if (!connection) return; | ||
if (packet.t === "VOICE_SERVER_UPDATE") return connection.setServerUpdate(packet.d); | ||
const userId = packet.d.user_id; | ||
if (userId !== this.manager.id) | ||
return; | ||
if (userId !== this.manager.id) return; | ||
connection.setStateUpdate(packet.d); | ||
@@ -300,15 +311,36 @@ } | ||
// src/connectors/libs/Seyfert.ts | ||
var Seyfert = class extends Connector { | ||
// sendPacket is where your library send packets to Discord Gateway | ||
sendPacket(shardId, payload, important) { | ||
return this.client.gateway.send(shardId, payload); | ||
} | ||
// getId is a getter where the lib stores the client user (the one logged in as a bot) id | ||
getId() { | ||
return this.client.botId; | ||
} | ||
// Listen attaches the event listener to the library you are using | ||
listen(nodes) { | ||
this.client.events.values.RAW = { | ||
data: { name: "raw" }, | ||
run: (packet) => { | ||
if (packet.t === "READY") return this.ready(nodes); | ||
return this.raw(packet); | ||
} | ||
}; | ||
} | ||
}; | ||
// src/guild/Connection.ts | ||
var import_events = require("events"); | ||
var Connection = class extends import_events.EventEmitter { | ||
var import_events2 = require("events"); | ||
var Connection = class extends import_events2.EventEmitter { | ||
/** | ||
* @param manager The manager of this connection | ||
* @param options The options to pass in connection creation | ||
* @param options.guildId GuildId in which voice channel to connect to is located | ||
* @param options.shardId ShardId in which the guild exists | ||
* @param options.channelId ChannelId of voice channel to connect to | ||
* @param options.deaf Optional boolean value to specify whether to deafen the current bot user | ||
* @param options.mute Optional boolean value to specify whether to mute the current bot user | ||
* @param options.getNode Optional move function for moving players around | ||
*/ | ||
* @param manager The manager of this connection | ||
* @param options The options to pass in connection creation | ||
* @param options.guildId GuildId in which voice channel to connect to is located | ||
* @param options.shardId ShardId in which the guild exists | ||
* @param options.channelId ChannelId of voice channel to connect to | ||
* @param options.deaf Optional boolean value to specify whether to deafen the current bot user | ||
* @param options.mute Optional boolean value to specify whether to mute the current bot user | ||
*/ | ||
constructor(manager, options) { | ||
@@ -330,6 +362,6 @@ super(); | ||
/** | ||
* Set the deafen status for the current bot user | ||
* @param deaf Boolean value to indicate whether to deafen or undeafen | ||
* @defaultValue false | ||
*/ | ||
* Set the deafen status for the current bot user | ||
* @param deaf Boolean value to indicate whether to deafen or undeafen | ||
* @defaultValue false | ||
*/ | ||
setDeaf(deaf = false) { | ||
@@ -340,6 +372,6 @@ this.deafened = deaf; | ||
/** | ||
* Set the mute status for the current bot user | ||
* @param mute Boolean value to indicate whether to mute or unmute | ||
* @defaultValue false | ||
*/ | ||
* Set the mute status for the current bot user | ||
* @param mute Boolean value to indicate whether to mute or unmute | ||
* @defaultValue false | ||
*/ | ||
setMute(mute = false) { | ||
@@ -350,8 +382,7 @@ this.muted = mute; | ||
/** | ||
* Disconnect the current bot user from the connected voice channel | ||
* @internal | ||
*/ | ||
* Disconnect the current bot user from the connected voice channel | ||
* @internal | ||
*/ | ||
disconnect() { | ||
if (this.state === 5 /* DISCONNECTED */) | ||
return; | ||
if (this.state === 5 /* DISCONNECTED */) return; | ||
this.channelId = null; | ||
@@ -366,8 +397,7 @@ this.deafened = false; | ||
/** | ||
* Connect the current bot user to a voice channel | ||
* @internal | ||
*/ | ||
* Connect the current bot user to a voice channel | ||
* @internal | ||
*/ | ||
async connect() { | ||
if (this.state === 0 /* CONNECTING */ || this.state === 2 /* CONNECTED */) | ||
return; | ||
if (this.state === 0 /* CONNECTING */ || this.state === 2 /* CONNECTED */) return; | ||
this.state = 0 /* CONNECTING */; | ||
@@ -379,3 +409,3 @@ this.sendVoiceUpdate(); | ||
try { | ||
const [status] = await (0, import_events.once)(this, "connectionUpdate", { signal: controller.signal }); | ||
const [status] = await (0, import_events2.once)(this, "connectionUpdate", { signal: controller.signal }); | ||
if (status !== 0 /* SESSION_READY */) { | ||
@@ -390,3 +420,4 @@ switch (status) { | ||
this.state = 2 /* CONNECTED */; | ||
} catch (error) { | ||
} catch (e) { | ||
const error = e; | ||
this.debug(`[Voice] </- [Discord] : Request Connection Failed | Guild: ${this.guildId}`); | ||
@@ -401,13 +432,13 @@ if (error.name === "AbortError") | ||
/** | ||
* Update Session ID, Channel ID, Deafen status and Mute status of this instance | ||
* | ||
* @param options.session_id ID of this session | ||
* @param options.channel_id ID of currently connected voice channel | ||
* @param options.self_deaf Boolean that indicates if the current bot user is deafened or not | ||
* @param options.self_mute Boolean that indicates if the current bot user is muted or not | ||
* @internal | ||
*/ | ||
* Updates SessionId, ChannelId, Deafen and Mute data of this instance | ||
* | ||
* @param options.session_id Id of the current session | ||
* @param options.channel_id Id of the connected voice channel | ||
* @param options.self_deaf Boolean that indicates if the current bot user is deafened or not | ||
* @param options.self_mute Boolean that indicates if the current bot user is muted or not | ||
* @internal | ||
*/ | ||
setStateUpdate({ session_id, channel_id, self_deaf, self_mute }) { | ||
this.lastChannelId = this.channelId?.repeat(1) || null; | ||
this.channelId = channel_id || null; | ||
this.lastChannelId = this.channelId?.repeat(1) ?? null; | ||
this.channelId = channel_id ?? null; | ||
if (this.channelId && this.lastChannelId !== this.channelId) { | ||
@@ -422,9 +453,9 @@ this.debug(`[Voice] <- [Discord] : Channel Moved | Old Channel: ${this.channelId} Guild: ${this.guildId}`); | ||
this.muted = self_mute; | ||
this.sessionId = session_id || null; | ||
this.sessionId = session_id ?? null; | ||
this.debug(`[Voice] <- [Discord] : State Update Received | Channel: ${this.channelId} Session ID: ${session_id} Guild: ${this.guildId}`); | ||
} | ||
/** | ||
* Sets the server update data for this connection | ||
* @internal | ||
*/ | ||
* Sets the server update data for this connection | ||
* @internal | ||
*/ | ||
setServerUpdate(data) { | ||
@@ -439,4 +470,4 @@ if (!data.endpoint) { | ||
} | ||
this.lastRegion = this.region?.repeat(1) || null; | ||
this.region = data.endpoint.split(".").shift()?.replace(/[0-9]/g, "") || null; | ||
this.lastRegion = this.region?.repeat(1) ?? null; | ||
this.region = data.endpoint.split(".").shift()?.replace(/[0-9]/g, "") ?? null; | ||
if (this.region && this.lastRegion !== this.region) { | ||
@@ -450,5 +481,5 @@ this.debug(`[Voice] <- [Discord] : Voice Region Moved | Old Region: ${this.lastRegion} New Region: ${this.region} Guild: ${this.guildId}`); | ||
/** | ||
* Send voice data to discord | ||
* @internal | ||
*/ | ||
* Send voice data to discord | ||
* @internal | ||
*/ | ||
sendVoiceUpdate() { | ||
@@ -458,6 +489,6 @@ this.send({ guild_id: this.guildId, channel_id: this.channelId, self_deaf: this.deafened, self_mute: this.muted }); | ||
/** | ||
* Send data to Discord | ||
* @param data The data to send | ||
* @internal | ||
*/ | ||
* Send data to Discord | ||
* @param data The data to send | ||
* @internal | ||
*/ | ||
send(data) { | ||
@@ -467,5 +498,5 @@ this.manager.connector.sendPacket(this.shardId, { op: 4, d: data }, false); | ||
/** | ||
* Emits a debug log | ||
* @internal | ||
*/ | ||
* Emits a debug log | ||
* @internal | ||
*/ | ||
debug(message) { | ||
@@ -477,8 +508,15 @@ this.manager.emit("debug", this.constructor.name, message); | ||
// src/guild/Player.ts | ||
var import_events2 = require("events"); | ||
var Player = class extends import_events2.EventEmitter { | ||
var PlayerEventType = /* @__PURE__ */ ((PlayerEventType2) => { | ||
PlayerEventType2["TRACK_START_EVENT"] = "TrackStartEvent"; | ||
PlayerEventType2["TRACK_END_EVENT"] = "TrackEndEvent"; | ||
PlayerEventType2["TRACK_EXCEPTION_EVENT"] = "TrackExceptionEvent"; | ||
PlayerEventType2["TRACK_STUCK_EVENT"] = "TrackStuckEvent"; | ||
PlayerEventType2["WEBSOCKET_CLOSED_EVENT"] = "WebSocketClosedEvent"; | ||
return PlayerEventType2; | ||
})(PlayerEventType || {}); | ||
var Player = class extends TypedEventEmitter { | ||
/** | ||
* @param node An instance of Node (Lavalink API wrapper) | ||
* @param connection An instance of connection class | ||
*/ | ||
* @param node An instance of Node (Lavalink API wrapper) | ||
* @param connection An instance of connection class | ||
*/ | ||
constructor(guildId, node) { | ||
@@ -500,3 +538,5 @@ super(); | ||
playerOptions: { | ||
encodedTrack: this.track, | ||
track: { | ||
encoded: this.track | ||
}, | ||
position: this.position, | ||
@@ -515,16 +555,15 @@ paused: this.paused, | ||
/** | ||
* Move player to another node | ||
* @param name? Name of node to move to, or the default ideal node | ||
* @returns true if the player was moved, false if not | ||
*/ | ||
* Move player to another node | ||
* @param name Name of node to move to, or the default ideal node | ||
* @returns true if the player was moved, false if not | ||
*/ | ||
async move(name) { | ||
const connection = this.node.manager.connections.get(this.guildId); | ||
const node = this.node.manager.nodes.get(name) || this.node.manager.options.nodeResolver(this.node.manager.nodes, connection); | ||
const node = this.node.manager.nodes.get(name) ?? this.node.manager.getIdealNode(connection); | ||
if (!node && ![...this.node.manager.nodes.values()].some((node2) => node2.state === 2 /* CONNECTED */)) | ||
throw new Error("No available nodes to move to"); | ||
if (!node || node.name === this.node.name || node.state !== 2 /* CONNECTED */) | ||
return false; | ||
if (!node || node.name === this.node.name || node.state !== 2 /* CONNECTED */) return false; | ||
let lastNode = this.node.manager.nodes.get(this.node.name); | ||
if (!lastNode || lastNode.state !== 2 /* CONNECTED */) | ||
lastNode = ShoukakuDefaults.nodeResolver(this.node.manager.nodes, connection); | ||
lastNode = this.node.manager.getIdealNode(connection); | ||
await this.destroy(); | ||
@@ -535,3 +574,3 @@ try { | ||
return true; | ||
} catch (error) { | ||
} catch { | ||
this.node = lastNode; | ||
@@ -543,4 +582,4 @@ await this.resume(); | ||
/** | ||
* Destroys the player in remote lavalink side | ||
*/ | ||
* Destroys the player in remote lavalink side | ||
*/ | ||
async destroy() { | ||
@@ -550,171 +589,117 @@ await this.node.rest.destroyPlayer(this.guildId); | ||
/** | ||
* Play a new track | ||
* @param playable Options for playing this track | ||
*/ | ||
async playTrack(playable) { | ||
const playerOptions = { | ||
encodedTrack: playable.track | ||
}; | ||
if (playable.options) { | ||
const { pause, startTime, endTime, volume } = playable.options; | ||
if (pause) | ||
playerOptions.paused = pause; | ||
if (startTime) | ||
playerOptions.position = startTime; | ||
if (endTime) | ||
playerOptions.endTime = endTime; | ||
if (volume) | ||
playerOptions.volume = volume; | ||
} | ||
this.track = playable.track; | ||
if (playerOptions.paused) | ||
this.paused = playerOptions.paused; | ||
if (playerOptions.position) | ||
this.position = playerOptions.position; | ||
if (playerOptions.volume) | ||
this.volume = playerOptions.volume; | ||
await this.node.rest.updatePlayer({ | ||
guildId: this.guildId, | ||
noReplace: playable.options?.noReplace ?? false, | ||
playerOptions | ||
}); | ||
* Play a new track | ||
* @param playable Options for playing this track | ||
* @param noReplace Set it to true if you don't want to replace the currently playing track | ||
*/ | ||
playTrack(playerOptions, noReplace = false) { | ||
return this.update(playerOptions, noReplace); | ||
} | ||
/** | ||
* Stop the currently playing track | ||
*/ | ||
async stopTrack() { | ||
this.position = 0; | ||
await this.node.rest.updatePlayer({ | ||
guildId: this.guildId, | ||
playerOptions: { encodedTrack: null } | ||
}); | ||
* Stop the currently playing track | ||
*/ | ||
stopTrack() { | ||
return this.update({ track: { encoded: null }, position: 0 }); | ||
} | ||
/** | ||
* Pause or unpause the currently playing track | ||
* @param paused Boolean value to specify whether to pause or unpause the current bot user | ||
*/ | ||
async setPaused(paused = true) { | ||
this.paused = paused; | ||
await this.node.rest.updatePlayer({ | ||
guildId: this.guildId, | ||
playerOptions: { paused } | ||
}); | ||
* Pause or unpause the currently playing track | ||
* @param paused Boolean value to specify whether to pause or unpause the current bot user | ||
*/ | ||
setPaused(paused = true) { | ||
return this.update({ paused }); | ||
} | ||
/** | ||
* Seek to a specific time in the currently playing track | ||
* @param position Position to seek to in milliseconds | ||
*/ | ||
async seekTo(position) { | ||
this.position = position; | ||
await this.node.rest.updatePlayer({ | ||
guildId: this.guildId, | ||
playerOptions: { position } | ||
}); | ||
* Seek to a specific time in the currently playing track | ||
* @param position Position to seek to in milliseconds | ||
*/ | ||
seekTo(position) { | ||
return this.update({ position }); | ||
} | ||
/** | ||
* Sets the global volume of the player | ||
* @param volume Target volume 0-1000 | ||
*/ | ||
async setGlobalVolume(volume) { | ||
this.volume = volume; | ||
await this.node.rest.updatePlayer({ | ||
guildId: this.guildId, | ||
playerOptions: { volume: this.volume } | ||
}); | ||
* Sets the global volume of the player | ||
* @param volume Target volume 0-1000 | ||
*/ | ||
setGlobalVolume(volume) { | ||
return this.update({ volume }); | ||
} | ||
/** | ||
* Sets the filter volume of the player | ||
* @param volume Target volume 0.0-5.0 | ||
*/ | ||
* Sets the filter volume of the player | ||
* @param volume Target volume 0.0-5.0 | ||
*/ | ||
async setFilterVolume(volume) { | ||
this.filters.volume = volume; | ||
await this.setFilters(this.filters); | ||
return this.setFilters({ volume }); | ||
} | ||
/** | ||
* Change the equalizer settings applied to the currently playing track | ||
* @param equalizer An array of objects that conforms to the Bands type that define volumes at different frequencies | ||
*/ | ||
* Change the equalizer settings applied to the currently playing track | ||
* @param equalizer An array of objects that conforms to the Bands type that define volumes at different frequencies | ||
*/ | ||
async setEqualizer(equalizer) { | ||
this.filters.equalizer = equalizer; | ||
await this.setFilters(this.filters); | ||
return this.setFilters({ equalizer }); | ||
} | ||
/** | ||
* Change the karaoke settings applied to the currently playing track | ||
* @param karaoke An object that conforms to the KaraokeSettings type that defines a range of frequencies to mute | ||
*/ | ||
async setKaraoke(karaoke) { | ||
this.filters.karaoke = karaoke || null; | ||
await this.setFilters(this.filters); | ||
* Change the karaoke settings applied to the currently playing track | ||
* @param karaoke An object that conforms to the KaraokeSettings type that defines a range of frequencies to mute | ||
*/ | ||
setKaraoke(karaoke) { | ||
return this.setFilters({ karaoke: karaoke ?? null }); | ||
} | ||
/** | ||
* Change the timescale settings applied to the currently playing track | ||
* @param timescale An object that conforms to the TimescaleSettings type that defines the time signature to play the audio at | ||
*/ | ||
async setTimescale(timescale) { | ||
this.filters.timescale = timescale || null; | ||
await this.setFilters(this.filters); | ||
* Change the timescale settings applied to the currently playing track | ||
* @param timescale An object that conforms to the TimescaleSettings type that defines the time signature to play the audio at | ||
*/ | ||
setTimescale(timescale) { | ||
return this.setFilters({ timescale: timescale ?? null }); | ||
} | ||
/** | ||
* Change the tremolo settings applied to the currently playing track | ||
* @param tremolo An object that conforms to the FreqSettings type that defines an oscillation in volume | ||
*/ | ||
async setTremolo(tremolo) { | ||
this.filters.tremolo = tremolo || null; | ||
await this.setFilters(this.filters); | ||
* Change the tremolo settings applied to the currently playing track | ||
* @param tremolo An object that conforms to the FreqSettings type that defines an oscillation in volume | ||
*/ | ||
setTremolo(tremolo) { | ||
return this.setFilters({ tremolo: tremolo ?? null }); | ||
} | ||
/** | ||
* Change the vibrato settings applied to the currently playing track | ||
* @param vibrato An object that conforms to the FreqSettings type that defines an oscillation in pitch | ||
*/ | ||
async setVibrato(vibrato) { | ||
this.filters.vibrato = vibrato || null; | ||
await this.setFilters(this.filters); | ||
* Change the vibrato settings applied to the currently playing track | ||
* @param vibrato An object that conforms to the FreqSettings type that defines an oscillation in pitch | ||
*/ | ||
setVibrato(vibrato) { | ||
return this.setFilters({ vibrato: vibrato ?? null }); | ||
} | ||
/** | ||
* Change the rotation settings applied to the currently playing track | ||
* @param rotation An object that conforms to the RotationSettings type that defines the frequency of audio rotating round the listener | ||
*/ | ||
async setRotation(rotation) { | ||
this.filters.rotation = rotation || null; | ||
await this.setFilters(this.filters); | ||
* Change the rotation settings applied to the currently playing track | ||
* @param rotation An object that conforms to the RotationSettings type that defines the frequency of audio rotating round the listener | ||
*/ | ||
setRotation(rotation) { | ||
return this.setFilters({ rotation: rotation ?? null }); | ||
} | ||
/** | ||
* Change the distortion settings applied to the currently playing track | ||
* @param distortion An object that conforms to DistortionSettings that defines distortions in the audio | ||
* @returns The current player instance | ||
*/ | ||
async setDistortion(distortion) { | ||
this.filters.distortion = distortion || null; | ||
await this.setFilters(this.filters); | ||
* Change the distortion settings applied to the currently playing track | ||
* @param distortion An object that conforms to DistortionSettings that defines distortions in the audio | ||
* @returns The current player instance | ||
*/ | ||
setDistortion(distortion) { | ||
return this.setFilters({ distortion: distortion ?? null }); | ||
} | ||
/** | ||
* Change the channel mix settings applied to the currently playing track | ||
* @param channelMix An object that conforms to ChannelMixSettings that defines how much the left and right channels affect each other (setting all factors to 0.5 causes both channels to get the same audio) | ||
*/ | ||
async setChannelMix(channelMix) { | ||
this.filters.channelMix = channelMix || null; | ||
await this.setFilters(this.filters); | ||
* Change the channel mix settings applied to the currently playing track | ||
* @param channelMix An object that conforms to ChannelMixSettings that defines how much the left and right channels affect each other (setting all factors to 0.5 causes both channels to get the same audio) | ||
*/ | ||
setChannelMix(channelMix) { | ||
return this.setFilters({ channelMix: channelMix ?? null }); | ||
} | ||
/** | ||
* Change the low pass settings applied to the currently playing track | ||
* @param lowPass An object that conforms to LowPassSettings that defines the amount of suppression on higher frequencies | ||
*/ | ||
async setLowPass(lowPass) { | ||
this.filters.lowPass = lowPass || null; | ||
await this.setFilters(this.filters); | ||
* Change the low pass settings applied to the currently playing track | ||
* @param lowPass An object that conforms to LowPassSettings that defines the amount of suppression on higher frequencies | ||
*/ | ||
setLowPass(lowPass) { | ||
return this.setFilters({ lowPass: lowPass ?? null }); | ||
} | ||
/** | ||
* Change the all filter settings applied to the currently playing track | ||
* @param filters An object that conforms to FilterOptions that defines all filters to apply/modify | ||
*/ | ||
async setFilters(filters) { | ||
this.filters = filters; | ||
await this.node.rest.updatePlayer({ | ||
guildId: this.guildId, | ||
playerOptions: { filters } | ||
}); | ||
* Change the all filter settings applied to the currently playing track | ||
* @param filters An object that conforms to FilterOptions that defines all filters to apply/modify | ||
*/ | ||
setFilters(filters) { | ||
return this.update({ filters }); | ||
} | ||
/** | ||
* Clear all filters applied to the currently playing track | ||
*/ | ||
* Clear all filters applied to the currently playing track | ||
*/ | ||
clearFilters() { | ||
@@ -735,42 +720,49 @@ return this.setFilters({ | ||
/** | ||
* Resumes the current track | ||
* @param options An object that conforms to ResumeOptions that specify behavior on resuming | ||
*/ | ||
async resume(options = {}) { | ||
* Resumes the current track | ||
* @param options An object that conforms to ResumeOptions that specify behavior on resuming | ||
* @param noReplace Set it to true if you don't want to replace the currently playing track | ||
*/ | ||
async resume(options = {}, noReplace = false) { | ||
const data = this.data; | ||
if (options.noReplace) | ||
data.noReplace = options.noReplace; | ||
if (options.startTime) | ||
data.playerOptions.position = options.startTime; | ||
if (options.endTime) | ||
data.playerOptions.position; | ||
if (options.pause) | ||
data.playerOptions.paused = options.pause; | ||
await this.update(data); | ||
if (typeof options.position === "number") | ||
data.playerOptions.position = options.position; | ||
if (typeof options.endTime === "number") | ||
data.playerOptions.endTime = options.endTime; | ||
if (typeof options.paused === "boolean") | ||
data.playerOptions.paused = options.paused; | ||
if (typeof options.volume === "number") | ||
data.playerOptions.volume = options.volume; | ||
await this.update(data.playerOptions, noReplace); | ||
this.emit("resumed", this); | ||
} | ||
/** | ||
* If you want to update the whole player yourself, sends raw update player info to lavalink | ||
*/ | ||
async update(updatePlayer) { | ||
const data = { ...updatePlayer, ...{ guildId: this.guildId, sessionId: this.node.sessionId } }; | ||
* If you want to update the whole player yourself, sends raw update player info to lavalink | ||
* @param playerOptions Options to update the player data | ||
* @param noReplace Set it to true if you don't want to replace the currently playing track | ||
*/ | ||
async update(playerOptions, noReplace = false) { | ||
const data = { | ||
guildId: this.guildId, | ||
noReplace, | ||
playerOptions | ||
}; | ||
await this.node.rest.updatePlayer(data); | ||
if (updatePlayer.playerOptions) { | ||
const options = updatePlayer.playerOptions; | ||
if (options.encodedTrack) | ||
this.track = options.encodedTrack; | ||
if (options.position) | ||
this.position = options.position; | ||
if (options.paused) | ||
this.paused = options.paused; | ||
if (options.filters) | ||
this.filters = options.filters; | ||
if (options.volume) | ||
this.volume = options.volume; | ||
if (!noReplace) this.paused = false; | ||
if (playerOptions.filters) { | ||
const filters = { ...this.filters, ...playerOptions.filters }; | ||
this.filters = filters; | ||
} | ||
if (typeof playerOptions.track !== "undefined") | ||
this.track = playerOptions.track.encoded ?? null; | ||
if (typeof playerOptions.paused === "boolean") | ||
this.paused = playerOptions.paused; | ||
if (typeof playerOptions.volume === "number") | ||
this.volume = playerOptions.volume; | ||
if (typeof playerOptions.position === "number") | ||
this.position = playerOptions.position; | ||
} | ||
/** | ||
* Cleans this player instance | ||
* @internal | ||
*/ | ||
* Cleans this player instance | ||
* @internal | ||
*/ | ||
clean() { | ||
@@ -784,5 +776,5 @@ this.removeAllListeners(); | ||
/** | ||
* Sends server update to lavalink | ||
* @internal | ||
*/ | ||
* Sends server update to lavalink | ||
* @internal | ||
*/ | ||
async sendServerUpdate(connection) { | ||
@@ -802,4 +794,4 @@ const playerUpdate = { | ||
/** | ||
* Handle player update data | ||
*/ | ||
* Handle player update data | ||
*/ | ||
onPlayerUpdate(json) { | ||
@@ -812,30 +804,29 @@ const { position, ping } = json.state; | ||
/** | ||
* Handle player events received from Lavalink | ||
* @param json JSON data from Lavalink | ||
* @internal | ||
*/ | ||
* Handle player events received from Lavalink | ||
* @param json JSON data from Lavalink | ||
* @internal | ||
*/ | ||
onPlayerEvent(json) { | ||
switch (json.type) { | ||
case "TrackStartEvent": | ||
if (this.track) | ||
this.track = json.track.encoded; | ||
case "TrackStartEvent" /* TRACK_START_EVENT */: | ||
if (this.track) this.track = json.track.encoded; | ||
this.emit("start", json); | ||
break; | ||
case "TrackEndEvent": | ||
case "TrackEndEvent" /* TRACK_END_EVENT */: | ||
this.emit("end", json); | ||
break; | ||
case "TrackStuckEvent": | ||
case "TrackStuckEvent" /* TRACK_STUCK_EVENT */: | ||
this.emit("stuck", json); | ||
break; | ||
case "TrackExceptionEvent": | ||
case "TrackExceptionEvent" /* TRACK_EXCEPTION_EVENT */: | ||
this.emit("exception", json); | ||
break; | ||
case "WebSocketClosedEvent": | ||
case "WebSocketClosedEvent" /* WEBSOCKET_CLOSED_EVENT */: | ||
this.emit("closed", json); | ||
break; | ||
default: | ||
this.node.emit( | ||
this.node.manager.emit( | ||
"debug", | ||
this.node.name, | ||
`[Player] -> [Node] : Unknown Player Event Type ${json.type} | Guild: ${this.guildId}` | ||
`[Player] -> [Node] : Unknown Player Event Type, Data => ${JSON.stringify(json)}` | ||
); | ||
@@ -846,5 +837,2 @@ } | ||
// src/node/Node.ts | ||
var import_events3 = require("events"); | ||
// src/node/Rest.ts | ||
@@ -861,14 +849,13 @@ var LoadType = /* @__PURE__ */ ((LoadType2) => { | ||
/** | ||
* @param node An instance of Node | ||
* @param options The options to initialize this rest class | ||
* @param options.name Name of this node | ||
* @param options.url URL of Lavalink | ||
* @param options.auth Credentials to access Lavalnk | ||
* @param options.secure Weather to use secure protocols or not | ||
* @param options.group Group of this node | ||
*/ | ||
* @param node An instance of Node | ||
* @param options The options to initialize this rest class | ||
* @param options.name Name of this node | ||
* @param options.url URL of Lavalink | ||
* @param options.auth Credentials to access Lavalnk | ||
* @param options.secure Weather to use secure protocols or not | ||
* @param options.group Group of this node | ||
*/ | ||
constructor(node, options) { | ||
this.node = node; | ||
this.url = `${options.secure ? "https" : "http"}://${options.url}`; | ||
this.version = `/v${4 /* REST_VERSION */}`; | ||
this.url = `${options.secure ? "https" : "http"}://${options.url}/v${Versions.REST_VERSION}`; | ||
this.auth = options.auth; | ||
@@ -880,6 +867,6 @@ } | ||
/** | ||
* Resolve a track | ||
* @param identifier Track ID | ||
* @returns A promise that resolves to a Lavalink response | ||
*/ | ||
* Resolve a track | ||
* @param identifier Track ID | ||
* @returns A promise that resolves to a Lavalink response | ||
*/ | ||
resolve(identifier) { | ||
@@ -893,6 +880,6 @@ const options = { | ||
/** | ||
* Decode a track | ||
* @param track Encoded track | ||
* @returns Promise that resolves to a track | ||
*/ | ||
* Decode a track | ||
* @param track Encoded track | ||
* @returns Promise that resolves to a track | ||
*/ | ||
decode(track) { | ||
@@ -906,5 +893,5 @@ const options = { | ||
/** | ||
* Gets all the player with the specified sessionId | ||
* @returns Promise that resolves to an array of Lavalink players | ||
*/ | ||
* Gets all the player with the specified sessionId | ||
* @returns Promise that resolves to an array of Lavalink players | ||
*/ | ||
async getPlayers() { | ||
@@ -918,5 +905,5 @@ const options = { | ||
/** | ||
* Gets all the player with the specified sessionId | ||
* @returns Promise that resolves to an array of Lavalink players | ||
*/ | ||
* Gets the player with the specified guildId | ||
* @returns Promise that resolves to a Lavalink player | ||
*/ | ||
getPlayer(guildId) { | ||
@@ -930,6 +917,6 @@ const options = { | ||
/** | ||
* Updates a Lavalink player | ||
* @param data SessionId from Discord | ||
* @returns Promise that resolves to a Lavalink player | ||
*/ | ||
* Updates a Lavalink player | ||
* @param data SessionId from Discord | ||
* @returns Promise that resolves to a Lavalink player | ||
*/ | ||
updatePlayer(data) { | ||
@@ -940,3 +927,3 @@ const options = { | ||
method: "PATCH", | ||
params: { noReplace: data.noReplace?.toString() || "false" }, | ||
params: { noReplace: data.noReplace?.toString() ?? "false" }, | ||
headers: { "Content-Type": "application/json" }, | ||
@@ -949,5 +936,5 @@ body: data.playerOptions | ||
/** | ||
* Deletes a Lavalink player | ||
* @param guildId guildId where this player is | ||
*/ | ||
* Deletes a Lavalink player | ||
* @param guildId guildId where this player is | ||
*/ | ||
async destroyPlayer(guildId) { | ||
@@ -961,7 +948,7 @@ const options = { | ||
/** | ||
* Updates the session with a resume boolean and timeout | ||
* @param resuming Whether resuming is enabled for this session or not | ||
* @param timeout Timeout to wait for resuming | ||
* @returns Promise that resolves to a Lavalink player | ||
*/ | ||
* Updates the session with a resume boolean and timeout | ||
* @param resuming Whether resuming is enabled for this session or not | ||
* @param timeout Timeout to wait for resuming | ||
* @returns Promise that resolves to a Lavalink player | ||
*/ | ||
updateSession(resuming, timeout) { | ||
@@ -979,5 +966,5 @@ const options = { | ||
/** | ||
* Gets the status of this node | ||
* @returns Promise that resolves to a node stats response | ||
*/ | ||
* Gets the status of this node | ||
* @returns Promise that resolves to a node stats response | ||
*/ | ||
stats() { | ||
@@ -991,5 +978,5 @@ const options = { | ||
/** | ||
* Get routeplanner status from Lavalink | ||
* @returns Promise that resolves to a routeplanner response | ||
*/ | ||
* Get routeplanner status from Lavalink | ||
* @returns Promise that resolves to a routeplanner response | ||
*/ | ||
getRoutePlannerStatus() { | ||
@@ -1003,5 +990,5 @@ const options = { | ||
/** | ||
* Release blacklisted IP address into pool of IPs | ||
* @param address IP address | ||
*/ | ||
* Release blacklisted IP address into pool of IPs | ||
* @param address IP address | ||
*/ | ||
async unmarkFailedAddress(address) { | ||
@@ -1019,4 +1006,4 @@ const options = { | ||
/** | ||
* Get Lavalink info | ||
*/ | ||
* Get Lavalink info | ||
*/ | ||
getLavalinkInfo() { | ||
@@ -1032,7 +1019,8 @@ const options = { | ||
/** | ||
* Make a request to Lavalink | ||
* @param fetchOptions.endpoint Lavalink endpoint | ||
* @param fetchOptions.options Options passed to fetch | ||
* @internal | ||
*/ | ||
* Make a request to Lavalink | ||
* @param fetchOptions.endpoint Lavalink endpoint | ||
* @param fetchOptions.options Options passed to fetch | ||
* @throws `RestError` when encountering a Lavalink error response | ||
* @internal | ||
*/ | ||
async fetch(fetchOptions) { | ||
@@ -1044,10 +1032,8 @@ const { endpoint, options } = fetchOptions; | ||
}; | ||
if (options.headers) | ||
headers = { ...headers, ...options.headers }; | ||
const url = new URL(`${this.url}${this.version}${endpoint}`); | ||
if (options.params) | ||
url.search = new URLSearchParams(options.params).toString(); | ||
if (options.headers) headers = { ...headers, ...options.headers }; | ||
const url = new URL(`${this.url}${endpoint}`); | ||
if (options.params) url.search = new URLSearchParams(options.params).toString(); | ||
const abortController = new AbortController(); | ||
const timeout = setTimeout(() => abortController.abort(), this.node.manager.options.restTimeout * 1e3); | ||
const method = options.method?.toUpperCase() || "GET"; | ||
const method = options.method?.toUpperCase() ?? "GET"; | ||
const finalFetchOptions = { | ||
@@ -1063,6 +1049,9 @@ method, | ||
const response = await request.json().catch(() => null); | ||
if (!response?.message) | ||
throw new Error(`Rest request failed with response code: ${request.status}`); | ||
else | ||
throw new Error(`Rest request failed with response code: ${request.status} | message: ${response.message}`); | ||
throw new RestError(response ?? { | ||
timestamp: Date.now(), | ||
status: request.status, | ||
error: "Unknown Error", | ||
message: "Unexpected error response from Lavalink server", | ||
path: endpoint | ||
}); | ||
} | ||
@@ -1076,24 +1065,36 @@ try { | ||
}; | ||
var RestError = class extends Error { | ||
constructor({ timestamp, status, error, trace, message, path }) { | ||
super(`Rest request failed with response code: ${status}${message ? ` | message: ${message}` : ""}`); | ||
this.name = "RestError"; | ||
this.timestamp = timestamp; | ||
this.status = status; | ||
this.error = error; | ||
this.trace = trace; | ||
this.message = message; | ||
this.path = path; | ||
Object.setPrototypeOf(this, new.target.prototype); | ||
} | ||
}; | ||
// src/node/Node.ts | ||
var import_ws = __toESM(require("ws")); | ||
var Node = class extends import_events3.EventEmitter { | ||
var Node = class extends TypedEventEmitter { | ||
/** | ||
* @param manager Shoukaku instance | ||
* @param options Options on creating this node | ||
* @param options.name Name of this node | ||
* @param options.url URL of Lavalink | ||
* @param options.auth Credentials to access Lavalnk | ||
* @param options.secure Whether to use secure protocols or not | ||
* @param options.group Group of this node | ||
*/ | ||
* @param manager Shoukaku instance | ||
* @param options Options on creating this node | ||
* @param options.name Name of this node | ||
* @param options.url URL of Lavalink | ||
* @param options.auth Credentials to access Lavalnk | ||
* @param options.secure Whether to use secure protocols or not | ||
* @param options.group Group of this node | ||
*/ | ||
constructor(manager, options) { | ||
super(); | ||
this.manager = manager; | ||
this.rest = new (this.manager.options.structures.rest || Rest)(this, options); | ||
this.rest = new (this.manager.options.structures.rest ?? Rest)(this, options); | ||
this.name = options.name; | ||
this.group = options.group; | ||
this.version = `/v${4 /* WEBSOCKET_VERSION */}`; | ||
this.url = `${options.secure ? "wss" : "ws"}://${options.url}`; | ||
this.auth = options.auth; | ||
this.url = `${options.secure ? "wss" : "ws"}://${options.url}/v${Versions.WEBSOCKET_VERSION}/websocket`; | ||
this.reconnects = 0; | ||
@@ -1109,10 +1110,9 @@ this.state = 5 /* DISCONNECTED */; | ||
/** | ||
* Penalties for load balancing | ||
* @returns Penalty score | ||
* @internal @readonly | ||
*/ | ||
* Penalties for load balancing | ||
* @returns Penalty score | ||
* @internal @readonly | ||
*/ | ||
get penalties() { | ||
let penalties = 0; | ||
if (!this.stats) | ||
return penalties; | ||
if (!this.stats) return penalties; | ||
penalties += this.stats.players; | ||
@@ -1127,5 +1127,5 @@ penalties += Math.round(Math.pow(1.05, 100 * this.stats.cpu.systemLoad) * 10 - 10); | ||
/** | ||
* If we should clean this node | ||
* @internal @readonly | ||
*/ | ||
* If we should clean this node | ||
* @internal @readonly | ||
*/ | ||
get shouldClean() { | ||
@@ -1135,12 +1135,10 @@ return this.destroyed || this.reconnects >= this.manager.options.reconnectTries; | ||
/** | ||
* Connect to Lavalink | ||
*/ | ||
* Connect to Lavalink | ||
*/ | ||
connect() { | ||
if (!this.manager.id) | ||
throw new Error("Don't connect a node when the library is not yet ready"); | ||
if (this.destroyed) | ||
throw new Error("You can't re-use the same instance of a node once disconnected, please re-add the node again"); | ||
if (!this.manager.id) throw new Error("Don't connect a node when the library is not yet ready"); | ||
if (this.destroyed) throw new Error("You can't re-use the same instance of a node once disconnected, please re-add the node again"); | ||
this.state = 0 /* CONNECTING */; | ||
const headers = { | ||
"Client-Name": this.manager.options.userAgent, | ||
"Client-Name": ShoukakuClientInfo, | ||
"User-Agent": this.manager.options.userAgent, | ||
@@ -1150,8 +1148,8 @@ "Authorization": this.auth, | ||
}; | ||
if (this.sessionId) | ||
headers["Resume-Key"] = this.sessionId; | ||
this.emit("debug", `[Socket] -> [${this.name}] : Connecting ${this.url}, Version: ${this.version}, Trying to resume? ${!!this.sessionId}`); | ||
if (this.sessionId && this.manager.options.resume) | ||
headers["Session-Id"] = this.sessionId; | ||
if (!this.initialized) | ||
this.initialized = true; | ||
const url = new URL(`${this.url}${this.version}/websocket`); | ||
this.emit("debug", `[Socket] -> [${this.name}] : Connecting to ${this.url} ...`); | ||
const url = new URL(this.url); | ||
this.ws = new import_ws.default(url.toString(), { headers }); | ||
@@ -1161,27 +1159,21 @@ this.ws.once("upgrade", (response) => this.open(response)); | ||
this.ws.on("error", (error) => this.error(error)); | ||
this.ws.on("message", (data) => this.message(data).catch((error) => this.error(error))); | ||
this.ws.on("message", (data) => void this.message(data).catch((error) => this.error(error))); | ||
} | ||
/** | ||
* Disconnect from lavalink | ||
* @param code Status code | ||
* @param reason Reason for disconnect | ||
*/ | ||
* Disconnect from Lavalink | ||
* @param code Status code | ||
* @param reason Reason for disconnect | ||
*/ | ||
disconnect(code, reason) { | ||
if (this.destroyed) | ||
return; | ||
this.destroyed = true; | ||
this.state = 4 /* DISCONNECTING */; | ||
if (this.ws) | ||
this.ws.close(code, reason); | ||
else | ||
this.clean(); | ||
this.internalDisconnect(code, reason); | ||
} | ||
/** | ||
* Handle connection open event from Lavalink | ||
* @param response Response from Lavalink | ||
* @internal | ||
*/ | ||
* Handle connection open event from Lavalink | ||
* @param response Response from Lavalink | ||
* @internal | ||
*/ | ||
open(response) { | ||
const resumed = response.headers["session-resumed"] === "true"; | ||
this.emit("debug", `[Socket] <-> [${this.name}] : Connection Handshake Done! ${this.url} | Upgrade Headers Resumed: ${resumed}`); | ||
const resumed = response.headers["session-resumed"]; | ||
this.emit("debug", `[Socket] <-> [${this.name}] : Connection Handshake Done! ${this.url} | Resumed Header Value: ${resumed}`); | ||
this.reconnects = 0; | ||
@@ -1191,12 +1183,9 @@ this.state = 1 /* NEARLY */; | ||
/** | ||
* Handle message from Lavalink | ||
* @param message JSON message | ||
* @internal | ||
*/ | ||
* Handle message from Lavalink | ||
* @param message JSON message | ||
* @internal | ||
*/ | ||
async message(message) { | ||
if (this.destroyed) | ||
return; | ||
const json = JSON.parse(message); | ||
if (!json) | ||
return; | ||
if (!json) return; | ||
this.emit("raw", json); | ||
@@ -1208,9 +1197,14 @@ switch (json.op) { | ||
break; | ||
case "ready" /* READY */: | ||
case "ready" /* READY */: { | ||
if (!json.sessionId) { | ||
this.emit("debug", `[Socket] -> [${this.name}] : No session id found from ready op? disconnecting and reconnecting to avoid issues`); | ||
return this.internalDisconnect(1e3); | ||
} | ||
this.sessionId = json.sessionId; | ||
const players = [...this.manager.players.values()].filter((player2) => player2.node.name === this.name); | ||
const resumeByLibrary = this.initialized && (players.length && this.manager.options.resumeByLibrary); | ||
if (!json.resumed && resumeByLibrary) { | ||
const players = [...this.manager.players.values()].filter((player) => player.node.name === this.name); | ||
let resumedByLibrary = false; | ||
if (!json.resumed && Boolean(this.initialized && (players.length && this.manager.options.resumeByLibrary))) { | ||
try { | ||
await this.resumePlayers(); | ||
resumedByLibrary = true; | ||
} catch (error) { | ||
@@ -1221,4 +1215,4 @@ this.error(error); | ||
this.state = 2 /* CONNECTED */; | ||
this.emit("debug", `[Socket] -> [${this.name}] : Lavalink is ready! | Lavalink resume: ${json.resumed} | Lib resume: ${!!resumeByLibrary}`); | ||
this.emit("ready", json.resumed || resumeByLibrary); | ||
this.emit("debug", `[Socket] -> [${this.name}] : Lavalink is ready! | Lavalink resume: ${json.resumed} | Lib resume: ${resumedByLibrary}`); | ||
this.emit("ready", json.resumed, resumedByLibrary); | ||
if (this.manager.options.resume) { | ||
@@ -1229,7 +1223,7 @@ await this.rest.updateSession(this.manager.options.resume, this.manager.options.resumeTimeout); | ||
break; | ||
} | ||
case "event" /* EVENT */: | ||
case "playerUpdate" /* PLAYER_UPDATE */: | ||
case "playerUpdate" /* PLAYER_UPDATE */: { | ||
const player = this.manager.players.get(json.guildId); | ||
if (!player) | ||
return; | ||
if (!player) return; | ||
if (json.op === "event" /* EVENT */) | ||
@@ -1240,23 +1234,24 @@ player.onPlayerEvent(json); | ||
break; | ||
} | ||
default: | ||
this.emit("debug", `[Player] -> [Node] : Unknown Message OP ${json.op}`); | ||
this.emit("debug", `[Player] -> [Node] : Unknown Message Op, Data => ${JSON.stringify(json)}`); | ||
} | ||
} | ||
/** | ||
* Handle closed event from lavalink | ||
* @param code Status close | ||
* @param reason Reason for connection close | ||
*/ | ||
* Handle closed event from lavalink | ||
* @param code Status close | ||
* @param reason Reason for connection close | ||
*/ | ||
close(code, reason) { | ||
this.emit("debug", `[Socket] <-/-> [${this.name}] : Connection Closed, Code: ${code || "Unknown Code"}`); | ||
this.emit("close", code, reason); | ||
this.emit("close", code, String(reason)); | ||
if (this.shouldClean) | ||
this.clean(); | ||
void this.clean(); | ||
else | ||
this.reconnect(); | ||
void this.reconnect(); | ||
} | ||
/** | ||
* To emit error events easily | ||
* @param error error message | ||
*/ | ||
* To emit error events easily | ||
* @param error error message | ||
*/ | ||
error(error) { | ||
@@ -1266,5 +1261,17 @@ this.emit("error", error); | ||
/** | ||
* Destroys the websocket connection | ||
* @internal | ||
*/ | ||
* Internal disconnect function | ||
* @internal | ||
*/ | ||
internalDisconnect(code, reason) { | ||
if (this.destroyed) return; | ||
this.state = 4 /* DISCONNECTING */; | ||
if (this.ws) | ||
this.ws.close(code, reason); | ||
else | ||
void this.clean(); | ||
} | ||
/** | ||
* Destroys the websocket connection | ||
* @internal | ||
*/ | ||
destroy(count = 0) { | ||
@@ -1274,6 +1281,4 @@ this.ws?.removeAllListeners(); | ||
this.ws = null; | ||
this.sessionId = null; | ||
this.state = 5 /* DISCONNECTED */; | ||
if (!this.shouldClean) | ||
return; | ||
if (!this.shouldClean) return; | ||
this.destroyed = true; | ||
@@ -1283,9 +1288,8 @@ this.emit("disconnect", count); | ||
/** | ||
* Cleans and moves players to other nodes if possible | ||
* @internal | ||
*/ | ||
* Cleans and moves players to other nodes if possible | ||
* @internal | ||
*/ | ||
async clean() { | ||
const move = this.manager.options.moveOnDisconnect; | ||
if (!move) | ||
return this.destroy(); | ||
if (!move) return this.destroy(); | ||
let count = 0; | ||
@@ -1301,10 +1305,8 @@ try { | ||
/** | ||
* Reconnect to Lavalink | ||
* @internal | ||
*/ | ||
* Reconnect to Lavalink | ||
* @internal | ||
*/ | ||
async reconnect() { | ||
if (this.state === 3 /* RECONNECTING */) | ||
return; | ||
if (this.state !== 5 /* DISCONNECTED */) | ||
this.destroy(); | ||
if (this.state === 3 /* RECONNECTING */) return; | ||
if (this.state !== 5 /* DISCONNECTED */) this.destroy(); | ||
this.state = 3 /* RECONNECTING */; | ||
@@ -1318,5 +1320,5 @@ this.reconnects++; | ||
/** | ||
* Tries to resume the players internally | ||
* @internal | ||
*/ | ||
* Tries to resume the players internally | ||
* @internal | ||
*/ | ||
async resumePlayers() { | ||
@@ -1338,5 +1340,5 @@ const playersWithData = []; | ||
/** | ||
* Tries to move the players to another node | ||
* @internal | ||
*/ | ||
* Tries to move the players to another node | ||
* @internal | ||
*/ | ||
async movePlayers() { | ||
@@ -1350,19 +1352,18 @@ const players = [...this.manager.players.values()]; | ||
// src/Shoukaku.ts | ||
var import_events4 = require("events"); | ||
var Shoukaku = class extends import_events4.EventEmitter { | ||
var Shoukaku = class extends TypedEventEmitter { | ||
/** | ||
* @param connector A Discord library connector | ||
* @param nodes An array that conforms to the NodeOption type that specifies nodes to connect to | ||
* @param options Options to pass to create this Shoukaku instance | ||
* @param options.resume Whether to resume a connection on disconnect to Lavalink (Server Side) (Note: DOES NOT RESUME WHEN THE LAVALINK SERVER DIES) | ||
* @param options.resumeTimeout Time to wait before lavalink starts to destroy the players of the disconnected client | ||
* @param options.resumeByLibrary Whether to resume the players by doing it in the library side (Client Side) (Note: TRIES TO RESUME REGARDLESS OF WHAT HAPPENED ON A LAVALINK SERVER) | ||
* @param options.reconnectTries Number of times to try and reconnect to Lavalink before giving up | ||
* @param options.reconnectInterval Timeout before trying to reconnect | ||
* @param options.restTimeout Time to wait for a response from the Lavalink REST API before giving up | ||
* @param options.moveOnDisconnect Whether to move players to a different Lavalink node when a node disconnects | ||
* @param options.userAgent User Agent to use when making requests to Lavalink | ||
* @param options.structures Custom structures for shoukaku to use | ||
* @param options.nodeResolver Used if you have custom lavalink node resolving | ||
*/ | ||
* @param connector A Discord library connector | ||
* @param nodes An array that conforms to the NodeOption type that specifies nodes to connect to | ||
* @param options Options to pass to create this Shoukaku instance | ||
* @param options.resume Whether to resume a connection on disconnect to Lavalink (Server Side) (Note: DOES NOT RESUME WHEN THE LAVALINK SERVER DIES) | ||
* @param options.resumeTimeout Time to wait before lavalink starts to destroy the players of the disconnected client | ||
* @param options.resumeByLibrary Whether to resume the players by doing it in the library side (Client Side) (Note: TRIES TO RESUME REGARDLESS OF WHAT HAPPENED ON A LAVALINK SERVER) | ||
* @param options.reconnectTries Number of times to try and reconnect to Lavalink before giving up | ||
* @param options.reconnectInterval Timeout before trying to reconnect | ||
* @param options.restTimeout Time to wait for a response from the Lavalink REST API before giving up | ||
* @param options.moveOnDisconnect Whether to move players to a different Lavalink node when a node disconnects | ||
* @param options.userAgent User Agent to use when making requests to Lavalink | ||
* @param options.structures Custom structures for shoukaku to use | ||
* @param options.nodeResolver Used if you have custom lavalink node resolving | ||
*/ | ||
constructor(connector, nodes, options = {}) { | ||
@@ -1379,9 +1380,17 @@ super(); | ||
/** | ||
* Add a Lavalink node to the pool of available nodes | ||
* @param options.name Name of this node | ||
* @param options.url URL of Lavalink | ||
* @param options.auth Credentials to access Lavalnk | ||
* @param options.secure Whether to use secure protocols or not | ||
* @param options.group Group of this node | ||
*/ | ||
* Gets an ideal node based on the nodeResolver you provided | ||
* @param connection Optional connection class for ideal node selection, if you use it | ||
* @returns An ideal node for you to do things with | ||
*/ | ||
getIdealNode(connection) { | ||
return this.options.nodeResolver(this.nodes, connection); | ||
} | ||
/** | ||
* Add a Lavalink node to the pool of available nodes | ||
* @param options.name Name of this node | ||
* @param options.url URL of Lavalink | ||
* @param options.auth Credentials to access Lavalnk | ||
* @param options.secure Whether to use secure protocols or not | ||
* @param options.group Group of this node | ||
*/ | ||
addNode(options) { | ||
@@ -1400,22 +1409,20 @@ const node = new Node(this, options); | ||
/** | ||
* Remove a Lavalink node from the pool of available nodes | ||
* @param name Name of the node | ||
* @param reason Reason of removing the node | ||
*/ | ||
* Remove a Lavalink node from the pool of available nodes | ||
* @param name Name of the node | ||
* @param reason Reason of removing the node | ||
*/ | ||
removeNode(name, reason = "Remove node executed") { | ||
const node = this.nodes.get(name); | ||
if (!node) | ||
throw new Error("The node name you specified doesn't exist"); | ||
if (!node) throw new Error("The node name you specified doesn't exist"); | ||
node.disconnect(1e3, reason); | ||
} | ||
/** | ||
* Joins a voice channel | ||
* @param options.guildId GuildId in which the ChannelId of the voice channel is located | ||
* @param options.shardId ShardId to track where this should send on sharded websockets, put 0 if you are unsharded | ||
* @param options.channelId ChannelId of the voice channel you want to connect to | ||
* @param options.deaf Optional boolean value to specify whether to deafen or undeafen the current bot user | ||
* @param options.mute Optional boolean value to specify whether to mute or unmute the current bot user | ||
* @returns The created player | ||
* @internal | ||
*/ | ||
* Joins a voice channel | ||
* @param options.guildId GuildId in which the ChannelId of the voice channel is located | ||
* @param options.shardId ShardId to track where this should send on sharded websockets, put 0 if you are unsharded | ||
* @param options.channelId ChannelId of the voice channel you want to connect to | ||
* @param options.deaf Optional boolean value to specify whether to deafen or undeafen the current bot user | ||
* @param options.mute Optional boolean value to specify whether to mute or unmute the current bot user | ||
* @returns The created player | ||
*/ | ||
async joinVoiceChannel(options) { | ||
@@ -1433,3 +1440,3 @@ if (this.connections.has(options.guildId)) | ||
try { | ||
const node = this.options.nodeResolver(this.nodes, connection); | ||
const node = this.getIdealNode(connection); | ||
if (!node) | ||
@@ -1439,5 +1446,4 @@ throw new Error("Can't find any nodes to connect on"); | ||
const onUpdate = (state) => { | ||
if (state !== 0 /* SESSION_READY */) | ||
return; | ||
player.sendServerUpdate(connection); | ||
if (state !== 0 /* SESSION_READY */) return; | ||
void player.sendServerUpdate(connection); | ||
}; | ||
@@ -1455,7 +1461,6 @@ await player.sendServerUpdate(connection); | ||
/** | ||
* Leaves a voice channel | ||
* @param guildId The id of the guild you want to delete | ||
* @returns The destroyed / disconnected player or undefined if none | ||
* @internal | ||
*/ | ||
* Leaves a voice channel | ||
* @param guildId The id of the guild you want to delete | ||
* @returns The destroyed / disconnected player or undefined if none | ||
*/ | ||
async leaveVoiceChannel(guildId) { | ||
@@ -1471,3 +1476,3 @@ const connection = this.connections.get(guildId); | ||
await player.destroy(); | ||
} catch (_) { | ||
} catch { | ||
} | ||
@@ -1479,8 +1484,8 @@ player.clean(); | ||
/** | ||
* Cleans the disconnected lavalink node | ||
* @param node The node to clean | ||
* @param args Additional arguments for Shoukaku to emit | ||
* @returns A Lavalink node or undefined | ||
* @internal | ||
*/ | ||
* Cleans the disconnected lavalink node | ||
* @param node The node to clean | ||
* @param args Additional arguments for Shoukaku to emit | ||
* @returns A Lavalink node or undefined | ||
* @internal | ||
*/ | ||
clean(node, ...args) { | ||
@@ -1502,3 +1507,5 @@ node.removeAllListeners(); | ||
Player, | ||
PlayerEventType, | ||
Rest, | ||
RestError, | ||
Shoukaku, | ||
@@ -1505,0 +1512,0 @@ Utils |
{ | ||
"name": "shoukaku", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"description": "A stable and updated wrapper around Lavalink", | ||
@@ -19,3 +19,3 @@ "main": "dist/index.js", | ||
"build:docs": "typedoc --theme default --readme README.md --out docs/ --entryPointStrategy expand src/.", | ||
"lint": "eslint --fix --ext .ts", | ||
"lint": "eslint .", | ||
"prepare": "npm run build:ts" | ||
@@ -45,16 +45,13 @@ }, | ||
"dependencies": { | ||
"ws": "^8.14.2" | ||
"ws": "^8.18.0" | ||
}, | ||
"devDependencies": { | ||
"@augu/eslint-config": "^5.0.0", | ||
"@types/node": "^20.10.5", | ||
"@types/node-fetch": "^2.6.9", | ||
"@types/ws": "^8.5.10", | ||
"@typescript-eslint/eslint-plugin": "^6.15.0", | ||
"@typescript-eslint/parser": "^6.15.0", | ||
"eslint": "^8.56.0", | ||
"tsup": "^8.0.1", | ||
"typedoc": "^0.25.4", | ||
"typescript": "^5.3.3" | ||
"@shipgirl/eslint-config": "^0.2.2", | ||
"@types/node": "^22.2.0", | ||
"@types/ws": "^8.5.12", | ||
"eslint": "^9.9.0", | ||
"tsup": "^8.2.4", | ||
"typedoc": "^0.26.5", | ||
"typescript": "^5.5.4" | ||
} | ||
} |
241
README.md
@@ -9,238 +9,77 @@ ## Shoukaku | ||
![GitHub issues](https://img.shields.io/github/issues-raw/Deivu/Shoukaku?style=flat-square) | ||
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/shoukaku?style=flat-square) | ||
![NPM](https://img.shields.io/npm/l/shoukaku?style=flat-square) | ||
<p align="center"> | ||
<img src="https://safe.saya.moe/lhvaWz3iP67f.webp"> | ||
<img src="https://safe.saya.moe/OlYoY5xxkMLO.png"> | ||
</p> | ||
> The ShipGirl Project, feat Shoukaku; ⓒ Azur Lane | ||
> Shoukaku, from Azur Lane, drawn by: elfenlied22 | ||
### Features | ||
✅ Stable | ||
- Stable | ||
✅ Documented | ||
- Updated | ||
✅ Updated | ||
- Documented | ||
✅ Extendable | ||
- Extendable | ||
✅ ESM & CommonJS supported | ||
- ESM & CommonJS supported | ||
✅ Very cute (Very Important) | ||
- Very cute (Very Important) | ||
### Supported Libraries | ||
Refer to [/src/connectors](https://github.com/Deivu/Shoukaku/tree/master/src/connectors) for list of supported libraries + how to support other libraries | ||
### Installation | ||
> `npm install shoukaku` | ||
### Documentation | ||
https://shoukaku.shipgirl.moe/ | ||
> https://guide.shoukaku.shipgirl.moe/ | ||
### Small code snippet examples | ||
### Getting Started | ||
> Initializing the library (Using Connector Discord.JS) | ||
> https://guide.shoukaku.shipgirl.moe/guides/1-getting-started/ | ||
```js | ||
const { Client } = require("discord.js"); | ||
const { Shoukaku, Connectors } = require("shoukaku"); | ||
const Nodes = [ | ||
{ | ||
name: "Localhost", | ||
url: "localhost:6969", | ||
auth: "re_aoharu", | ||
}, | ||
]; | ||
const client = new Client(); | ||
const shoukaku = new Shoukaku(new Connectors.DiscordJS(client), Nodes); | ||
// ALWAYS handle error, logging it will do | ||
shoukaku.on("error", (_, error) => console.error(error)); | ||
client.login("token"); | ||
// If you want shoukaku to be available on client, then bind it to it, here is one example of it | ||
client.shoukaku = shoukaku; | ||
``` | ||
### Supported Libraries | ||
> Never initialize Shoukaku like this, or else she will never initialize, start shoukaku before you call `client.login()` | ||
> https://guide.shoukaku.shipgirl.moe/guides/5-connectors/ | ||
```js | ||
// NEVER DO THIS, OR SHOUKAKU WILL NEVER INITIALIZE | ||
client.on("ready", () => { | ||
client.shoukaku = new Shoukaku(new Connectors.DiscordJS(client), Nodes); | ||
}); | ||
``` | ||
### Example Bot | ||
> Join a voice channel, search for a track, play the track, then disconnect after 30 seconds | ||
> https://github.com/Deivu/Kongou | ||
```js | ||
const player = await shoukaku.joinVoiceChannel({ | ||
guildId: "your_guild_id", | ||
channelId: "your_channel_id", | ||
shardId: 0, // if unsharded it will always be zero (depending on your library implementation) | ||
}); | ||
// player is created, now search for a track | ||
const result = await player.node.rest.resolve("scsearch:snowhalation"); | ||
if (!result?.tracks.length) return; | ||
const metadata = result.tracks.shift(); | ||
// play the searched track | ||
await player.playTrack({ track: metadata.encoded }); | ||
// disconnect after 30 seconds | ||
setTimeout(() => shoukaku.leaveVoiceChannel(player.guildId), 30000).unref(); | ||
``` | ||
### Configuration Options | ||
> Playing a track and changing a playback option (in this example, volume) | ||
```js | ||
await player.playTrack({ track: metadata.encoded }); | ||
await player.setGlobalVolume(50); | ||
// Parameters for main class init, Options is the Configuration Options | ||
new Shoukaku(new Connectors.DiscordJS(client), Nodes, Options); | ||
``` | ||
> Updating the whole player if you don\'t want to use my helper functions | ||
| Option | Type | Default | Description | Notes | | ||
| ---------------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------------------ | ------------------------ | | ||
| resume | boolean | false | If you want to enable resuming when your connection when your connection to lavalink disconnects | | | ||
| resumeTimeout | number | 30 | Timeout before lavalink destroys the players on a disconnect | In seconds | | ||
| resumeByLibrary | boolean | false | If you want to force resume players no matter what even if it's not resumable by lavalink | | | ||
| reconnectTries | number | 3 | Number of tries to reconnect to lavalink before disconnecting | | | ||
| reconnectInterval | number | 5 | Timeout between reconnects | In seconds | | ||
| restTimeout | number | 60 | Maximum amount of time to wait for rest lavalink api requests | In seconds | | ||
| moveOnDisconnect | boolean | false | Whether to move players to a different lavalink node when a node disconnects | | | ||
| userAgent | string | (auto) | Changes the user-agent used for lavalink requests | Not recommeded to change | | ||
| structures | Object{rest?, player?} | {} | Custom structures for shoukaku to use | | | ||
| voiceConnectionTimeout | number | 15 | Maximum amount of time to wait for a join voice channel command | In seconds | | ||
| nodeResolver | function | function | Custom node resolver if you want to have your own method of getting the ideal node | | | ||
```js | ||
await player.update({ ...playerOptions }); | ||
``` | ||
### Wrappers | ||
> Setting a custom get node ideal function | ||
| Name | Link | Description | | ||
| -------- | --------------------------------------------- | -------------------------------------------------------- | | ||
| Kazagumo | [Github](https://github.com/Takiyo0/Kazagumo) | A wrapper for Shoukaku that has an internal queue system | | ||
```js | ||
const shoukaku = new Shoukaku( | ||
new Connectors.DiscordJS(client), | ||
[{ ...yourNodeOptions }], | ||
{ | ||
...yourShoukakuOptions, | ||
nodeResolver: (nodes, connection) => getYourIdealNode(nodes, connection), | ||
} | ||
); | ||
const player = await shoukaku.joinVoiceChannel({ | ||
guildId: "your_guild_id", | ||
channelId: "your_channel_id", | ||
shardId: 0, | ||
}); | ||
``` | ||
> Open a pr if you want to add a wrapper here | ||
### Updating from V3 -> V4 (notable changes) | ||
> The way of joining and leaving voice channels is now different | ||
```js | ||
const { Client } = require("discord.js"); | ||
const { Shoukaku, Connectors } = require("shoukaku"); | ||
const Nodes = [ | ||
{ | ||
name: "Localhost", | ||
url: "localhost:6969", | ||
auth: "marin_kitagawa", | ||
}, | ||
]; | ||
const client = new Client(); | ||
const shoukaku = new Shoukaku(new Connectors.DiscordJS(client), Nodes); | ||
shoukaku.on("error", (_, error) => console.error(error)); | ||
client.login("token"); | ||
client.once("ready", async () => { | ||
// get a node with least load to resolve a track | ||
const node = shoukaku.options.nodeResolver(shoukaku.nodes); | ||
const result = await node.rest.resolve("scsearch:snowhalation"); | ||
if (!result?.tracks.length) return; | ||
// we now have a track metadata, we can use this to play tracks | ||
const metadata = result.tracks.shift(); | ||
// you now join a voice channel by querying the main shoukaku class, not on the node anymore | ||
const player = await shoukaku.joinVoiceChannel({ | ||
guildId: "your_guild_id", | ||
channelId: "your_channel_id", | ||
shardId: 0, // if unsharded it will always be zero (depending on your library implementation) | ||
}); | ||
// if you want you can also use the player.node property after it connects to resolve tracks | ||
const result_2 = await player.node.rest.resolve("scsearch:snowhalation"); | ||
console.log(result_2.tracks.shift()); | ||
// now we can play the track | ||
await player.playTrack({ track: metadata.encoded }); | ||
setTimeout(async () => { | ||
// simulate a timeout event, after specific amount of time, we leave the voice channel | ||
// you now destroy players / leave voice channels by calling leaveVoiceChannel in main shoukaku class | ||
await shoukaku.leaveVoiceChannel(player.guildId); | ||
}, 30000); | ||
}); | ||
``` | ||
> Usual player methods now return promises | ||
```js | ||
await player.playTrack(...data); | ||
await player.stopTrack(); | ||
``` | ||
> There are 2 kinds of volumes you can set, global and filter | ||
```js | ||
// global volume accepts 0-1000 as it's values | ||
await player.setGlobalVolume(100); | ||
// to check the current global volume | ||
console.log(player.volume); | ||
// filter volume accepts 0.0-5.0 as it's values | ||
await player.setFilterVolume(1.0); | ||
// to check the current filter volume (filters.volume can be undefined) | ||
console.log(player.filters.volume); | ||
``` | ||
> There are other internal changes like | ||
```js | ||
// new variable in shoukaku class, which handles the "connection data" of discord only | ||
console.log(shoukaku.connections); | ||
// players are moved from `node.players` to `shoukaku.players` | ||
console.log(shoukaku.players); | ||
// getNode() is removed in favor of joinVoiceChannel, you can still get the default least loaded node via `shoukaku.options.nodeResolver()` | ||
const player = await shoukaku.joinVoiceChannel({ | ||
guildId: "your_guild_id", | ||
channelId: "your_channel_id", | ||
shardId: 0, | ||
}); | ||
// you can supply a custom node resolver for your own way of getting an ideal node by supplying the nodeResolver option in Shoukaku options | ||
const ShoukakuOptions = { | ||
...yourShoukakuOptions, | ||
nodeResolver: (nodes, connection) => getYourIdealNode(nodes, connection), | ||
}; | ||
// and other changes I'm not able to document(?); | ||
``` | ||
### Shoukaku's options | ||
| Option | Type | Default | Description | | ||
| ---------------------- | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| resume | boolean | false | Whether to resume a connection on disconnect to Lavalink (Server Side) (Note: DOES NOT RESUME WHEN THE LAVALINK SERVER DIES) | | ||
| resumeTimeout | number | 30 | Timeout before resuming a connection **in seconds** | | ||
| resumeByLibrary | boolean | false | Whether to resume the players by doing it in the library side (Client Side) (Note: TRIES TO RESUME REGARDLESS OF WHAT HAPPENED ON A LAVALINK SERVER) | | ||
| reconnectTries | number | 3 | Number of times to try and reconnect to Lavalink before giving up | | ||
| reconnectInterval | number | 5 | Timeout before trying to reconnect **in seconds** | | ||
| restTimeout | number | 60 | Time to wait for a response from the Lavalink REST API before giving up **in seconds** | | ||
| moveOnDisconnect | boolean | false | Whether to move players to a different Lavalink node when a node disconnects | | ||
| userAgent | string | (auto) | User Agent to use when making requests to Lavalink | | ||
| structures | Object{rest?, player?} | {} | Custom structures for shoukaku to use | | ||
| voiceConnectionTimeout | number | 15 | Timeout before abort connection **in seconds** | | ||
| nodeResolver | function | function | Custom node resolver if you want to have your own method of getting the ideal node | | ||
### Plugins list | ||
> Open a pr to add your plugin here | ||
| Name | Link | Description | | ||
| ------------ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | ||
| Kazagumo | [Github](https://github.com/Takiyo0/Kazagumo) | A Shoukaku wrapper that have built-in queue system | | ||
| stone-deezer | [NPM](https://www.npmjs.com/package/stone-deezer) | A plugin to simplify deezer links and then play it from available sources (**REQUIRES [KAZAGUMO](https://github.com/Takiyo0/Kazagumo)**) | | ||
### Other Links | ||
[Support](https://discord.gg/FVqbtGu) (#Development) | [Lavalink](https://github.com/freyacodes/Lavalink) | ||
- [Discord](https://discord.gg/FVqbtGu) | ||
### Implementation (Discord.JS) | ||
- [Lavalink](https://github.com/lavalink-devs/Lavalink) | ||
> [Kongou](https://github.com/Deivu/Kongou) | ||
### Code made with ❤ by @ichimakase (Saya) | ||
### Made with ❤ by | ||
> @ichimakase | ||
> The Shipgirl Project |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
7
10
4026
374677
84
Updatedws@^8.18.0