lavalink-client 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -152
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/structures/Filters.d.ts +231 -0
- package/dist/cjs/structures/Filters.js +481 -0
- package/dist/cjs/structures/LavalinkManager.d.ts +124 -0
- package/dist/cjs/structures/LavalinkManager.js +168 -0
- package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/cjs/structures/LavalinkManagerStatics.js +84 -0
- package/dist/cjs/structures/Node.d.ts +245 -0
- package/dist/cjs/structures/Node.js +602 -0
- package/dist/cjs/structures/NodeManager.d.ts +61 -0
- package/dist/cjs/structures/NodeManager.js +35 -0
- package/dist/cjs/structures/Player.d.ts +191 -0
- package/dist/cjs/structures/Player.js +395 -0
- package/dist/cjs/structures/Queue.d.ts +107 -0
- package/dist/cjs/structures/Queue.js +215 -0
- package/dist/cjs/structures/Track.d.ts +47 -0
- package/dist/cjs/structures/Track.js +2 -0
- package/dist/cjs/structures/Utils.d.ts +258 -0
- package/dist/cjs/structures/Utils.js +179 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/structures/Filters.d.ts +231 -0
- package/dist/esm/structures/Filters.js +477 -0
- package/dist/esm/structures/LavalinkManager.d.ts +124 -0
- package/dist/esm/structures/LavalinkManager.js +164 -0
- package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/esm/structures/LavalinkManagerStatics.js +81 -0
- package/dist/esm/structures/Node.d.ts +245 -0
- package/dist/esm/structures/Node.js +597 -0
- package/dist/esm/structures/NodeManager.d.ts +61 -0
- package/dist/esm/structures/NodeManager.js +31 -0
- package/dist/esm/structures/Player.d.ts +191 -0
- package/dist/esm/structures/Player.js +391 -0
- package/dist/esm/structures/Queue.d.ts +107 -0
- package/dist/esm/structures/Queue.js +208 -0
- package/dist/esm/structures/Track.d.ts +47 -0
- package/dist/esm/structures/Track.js +1 -0
- package/dist/esm/structures/Utils.d.ts +258 -0
- package/dist/esm/structures/Utils.js +173 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +13 -0
- package/dist/structures/Filters.d.ts +230 -0
- package/dist/structures/Filters.js +472 -0
- package/dist/structures/LavalinkManager.d.ts +47 -0
- package/dist/structures/LavalinkManager.js +36 -0
- package/dist/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/structures/LavalinkManagerStatics.js +76 -0
- package/dist/structures/Node.d.ts +171 -0
- package/dist/structures/Node.js +462 -0
- package/dist/structures/NodeManager.d.ts +58 -0
- package/dist/structures/NodeManager.js +25 -0
- package/dist/structures/Player.d.ts +101 -0
- package/dist/structures/Player.js +232 -0
- package/dist/structures/PlayerManager.d.ts +62 -0
- package/dist/structures/PlayerManager.js +26 -0
- package/dist/structures/Queue.d.ts +93 -0
- package/dist/structures/Queue.js +160 -0
- package/dist/structures/QueueManager.d.ts +77 -0
- package/dist/structures/QueueManager.js +74 -0
- package/dist/structures/Track.d.ts +27 -0
- package/dist/structures/Track.js +2 -0
- package/dist/structures/Utils.d.ts +183 -0
- package/dist/structures/Utils.js +43 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/structures/Filters.d.ts +231 -0
- package/dist/types/structures/LavalinkManager.d.ts +124 -0
- package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/types/structures/Node.d.ts +245 -0
- package/dist/types/structures/NodeManager.d.ts +61 -0
- package/dist/types/structures/Player.d.ts +191 -0
- package/dist/types/structures/Queue.d.ts +107 -0
- package/dist/types/structures/Track.d.ts +47 -0
- package/dist/types/structures/Utils.d.ts +258 -0
- package/package.json +63 -26
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { FilterManager, LavalinkFilterData } from "./Filters";
|
|
2
|
+
import { LavalinkManager } from "./LavalinkManager";
|
|
3
|
+
import { LavalinkNode } from "./Node";
|
|
4
|
+
import { Queue } from "./Queue";
|
|
5
|
+
import { Track } from "./Track";
|
|
6
|
+
import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult } from "./Utils";
|
|
7
|
+
type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted";
|
|
8
|
+
export type DestroyReasonsType = PlayerDestroyReasons | string;
|
|
9
|
+
export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
|
|
10
|
+
export type RepeatMode = "queue" | "track" | "off";
|
|
11
|
+
export interface PlayerOptions {
|
|
12
|
+
guildId: string;
|
|
13
|
+
voiceChannelId: string;
|
|
14
|
+
volume?: number;
|
|
15
|
+
vcRegion?: string;
|
|
16
|
+
selfDeaf?: boolean;
|
|
17
|
+
selfMute?: boolean;
|
|
18
|
+
textChannelId?: string;
|
|
19
|
+
node?: LavalinkNode | string;
|
|
20
|
+
instaUpdateFiltersFix?: boolean;
|
|
21
|
+
applyVolumeAsFilter?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface PlayOptions {
|
|
24
|
+
/** Which Track to play | don't provide, if it should pick from the Queue */
|
|
25
|
+
track?: Track;
|
|
26
|
+
/** Encoded Track to use, instead of the queue system... */
|
|
27
|
+
encodedTrack?: string | null;
|
|
28
|
+
/** Encoded Track to use&search, instead of the queue system (yt only)... */
|
|
29
|
+
identifier?: string;
|
|
30
|
+
/** The position to start the track. */
|
|
31
|
+
position?: number;
|
|
32
|
+
/** The position to end the track. */
|
|
33
|
+
endTime?: number;
|
|
34
|
+
/** Whether to not replace the track if a play payload is sent. */
|
|
35
|
+
noReplace?: boolean;
|
|
36
|
+
/** If to start "paused" */
|
|
37
|
+
paused?: boolean;
|
|
38
|
+
/** The Volume to start with */
|
|
39
|
+
volume?: number;
|
|
40
|
+
/** The Lavalink Filters to use | only with the new REST API */
|
|
41
|
+
filters?: Partial<LavalinkFilterData>;
|
|
42
|
+
voice?: LavalinkPlayerVoiceOptions;
|
|
43
|
+
}
|
|
44
|
+
export interface Player {
|
|
45
|
+
filterManager: FilterManager;
|
|
46
|
+
LavalinkManager: LavalinkManager;
|
|
47
|
+
options: PlayerOptions;
|
|
48
|
+
node: LavalinkNode;
|
|
49
|
+
queue: Queue;
|
|
50
|
+
}
|
|
51
|
+
export declare class Player {
|
|
52
|
+
/** The Guild Id of the Player */
|
|
53
|
+
guildId: string;
|
|
54
|
+
/** The Voice Channel Id of the Player */
|
|
55
|
+
voiceChannelId: string | null;
|
|
56
|
+
/** The Text Channel Id of the Player */
|
|
57
|
+
textChannelId: string | null;
|
|
58
|
+
/** States if the Bot is supposed to be outputting audio */
|
|
59
|
+
playing: boolean;
|
|
60
|
+
/** States if the Bot is paused or not */
|
|
61
|
+
paused: boolean;
|
|
62
|
+
/** Repeat Mode of the Player */
|
|
63
|
+
repeatMode: RepeatMode;
|
|
64
|
+
/** Player's ping */
|
|
65
|
+
ping: {
|
|
66
|
+
lavalink: number;
|
|
67
|
+
ws: number;
|
|
68
|
+
};
|
|
69
|
+
/** The Display Volume */
|
|
70
|
+
volume: number;
|
|
71
|
+
/** The Volume Lavalink actually is outputting */
|
|
72
|
+
lavalinkVolume: number;
|
|
73
|
+
/** The current Positin of the player (Calculated) */
|
|
74
|
+
position: number;
|
|
75
|
+
/** The current Positin of the player (from Lavalink) */
|
|
76
|
+
lastPosition: number;
|
|
77
|
+
/** When the player was created [Timestamp in Ms] (from lavalink) */
|
|
78
|
+
createdTimeStamp: number;
|
|
79
|
+
/** The Player Connection's State (from Lavalink) */
|
|
80
|
+
connected: boolean | undefined;
|
|
81
|
+
/** Voice Server Data (from Lavalink) */
|
|
82
|
+
voice: LavalinkPlayerVoiceOptions;
|
|
83
|
+
private readonly data;
|
|
84
|
+
/**
|
|
85
|
+
* Create a new Player
|
|
86
|
+
* @param options
|
|
87
|
+
* @param LavalinkManager
|
|
88
|
+
*/
|
|
89
|
+
constructor(options: PlayerOptions, LavalinkManager: LavalinkManager);
|
|
90
|
+
/**
|
|
91
|
+
* Set custom data.
|
|
92
|
+
* @param key
|
|
93
|
+
* @param value
|
|
94
|
+
*/
|
|
95
|
+
set(key: string, value: unknown): void;
|
|
96
|
+
/**
|
|
97
|
+
* Get custom data.
|
|
98
|
+
* @param key
|
|
99
|
+
*/
|
|
100
|
+
get<T>(key: string): T;
|
|
101
|
+
/**
|
|
102
|
+
* CLears all the custom data.
|
|
103
|
+
*/
|
|
104
|
+
clearData(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Get all custom Data
|
|
107
|
+
*/
|
|
108
|
+
getAllData(): Record<string, unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* Play the next track from the queue / a specific track, with playoptions for Lavalink
|
|
111
|
+
* @param options
|
|
112
|
+
*/
|
|
113
|
+
play(options?: Partial<PlayOptions>): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Set the Volume for the Player
|
|
116
|
+
* @param volume The Volume in percent
|
|
117
|
+
* @param ignoreVolumeDecrementer If it should ignore the volumedecrementer option
|
|
118
|
+
*/
|
|
119
|
+
setVolume(volume: number, ignoreVolumeDecrementer?: boolean): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
*
|
|
122
|
+
* @param query Query for your data
|
|
123
|
+
* @param requestUser
|
|
124
|
+
*/
|
|
125
|
+
search(query: {
|
|
126
|
+
query: string;
|
|
127
|
+
source?: SearchPlatform;
|
|
128
|
+
} | string, requestUser: unknown): Promise<SearchResult>;
|
|
129
|
+
/**
|
|
130
|
+
* Pause the player
|
|
131
|
+
*/
|
|
132
|
+
pause(): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Resume the Player
|
|
135
|
+
*/
|
|
136
|
+
resume(): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Seek to a specific Position
|
|
139
|
+
* @param position
|
|
140
|
+
*/
|
|
141
|
+
seek(position: number): Promise<any>;
|
|
142
|
+
/**
|
|
143
|
+
* Set the Repeatmode of the Player
|
|
144
|
+
* @param repeatMode
|
|
145
|
+
*/
|
|
146
|
+
setRepeatMode(repeatMode: RepeatMode): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Skip the current song, or a specific amount of songs
|
|
149
|
+
* @param amount provide the index of the next track to skip to
|
|
150
|
+
*/
|
|
151
|
+
skip(skipTo?: number): Promise<true | void>;
|
|
152
|
+
/**
|
|
153
|
+
* Connects the Player to the Voice Channel
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
connect(): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Disconnects the Player from the Voice Channel, but keeps the player in the cache
|
|
159
|
+
* @param force If false it throws an error, if player thinks it's already disconnected
|
|
160
|
+
* @returns
|
|
161
|
+
*/
|
|
162
|
+
disconnect(force?: boolean): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Destroy the player and disconnect from the voice channel
|
|
165
|
+
*/
|
|
166
|
+
destroy(reason?: string): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Move the player on a different Audio-Node
|
|
169
|
+
* @param newNode New Node / New Node Id
|
|
170
|
+
*/
|
|
171
|
+
changeNode(newNode: LavalinkNode | string): Promise<string>;
|
|
172
|
+
/** Converts the Player including Queue to a Json state */
|
|
173
|
+
toJSON(): {
|
|
174
|
+
guildId: string;
|
|
175
|
+
voiceChannelId: string;
|
|
176
|
+
textChannelId: string;
|
|
177
|
+
position: number;
|
|
178
|
+
lastPosition: number;
|
|
179
|
+
volume: number;
|
|
180
|
+
lavalinkVolume: number;
|
|
181
|
+
repeatMode: RepeatMode;
|
|
182
|
+
paused: boolean;
|
|
183
|
+
playing: boolean;
|
|
184
|
+
createdTimeStamp: number;
|
|
185
|
+
filters: {};
|
|
186
|
+
equalizer: import("./Filters").EQBand[];
|
|
187
|
+
queue: import("./Queue").StoredQueue;
|
|
188
|
+
nodeId: string;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export {};
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { FilterManager } from "./Filters";
|
|
2
|
+
import { DefaultSources } from "./LavalinkManagerStatics";
|
|
3
|
+
import { Queue, QueueSaver } from "./Queue";
|
|
4
|
+
import { queueTrackEnd } from "./Utils";
|
|
5
|
+
export const DestroyReasons = {
|
|
6
|
+
QueueEmpty: "QueueEmpty",
|
|
7
|
+
NodeDestroy: "NodeDestroy",
|
|
8
|
+
NodeDeleted: "NodeDeleted",
|
|
9
|
+
LavalinkNoVoice: "LavalinkNoVoice",
|
|
10
|
+
NodeReconnectFail: "NodeReconnectFail",
|
|
11
|
+
Disconnected: "Disconnected",
|
|
12
|
+
PlayerReconnectFail: "PlayerReconnectFail",
|
|
13
|
+
ChannelDeleted: "ChannelDeleted"
|
|
14
|
+
};
|
|
15
|
+
export class Player {
|
|
16
|
+
/** The Guild Id of the Player */
|
|
17
|
+
guildId;
|
|
18
|
+
/** The Voice Channel Id of the Player */
|
|
19
|
+
voiceChannelId = null;
|
|
20
|
+
/** The Text Channel Id of the Player */
|
|
21
|
+
textChannelId = null;
|
|
22
|
+
/** States if the Bot is supposed to be outputting audio */
|
|
23
|
+
playing = false;
|
|
24
|
+
/** States if the Bot is paused or not */
|
|
25
|
+
paused = false;
|
|
26
|
+
/** Repeat Mode of the Player */
|
|
27
|
+
repeatMode = "off";
|
|
28
|
+
/** Player's ping */
|
|
29
|
+
ping = {
|
|
30
|
+
/* Response time for rest actions with Lavalink Server */
|
|
31
|
+
lavalink: 0,
|
|
32
|
+
/* Latency of the Discord's Websocket Voice Server */
|
|
33
|
+
ws: 0
|
|
34
|
+
};
|
|
35
|
+
/** The Display Volume */
|
|
36
|
+
volume = 100;
|
|
37
|
+
/** The Volume Lavalink actually is outputting */
|
|
38
|
+
lavalinkVolume = 100;
|
|
39
|
+
/** The current Positin of the player (Calculated) */
|
|
40
|
+
position = 0;
|
|
41
|
+
/** The current Positin of the player (from Lavalink) */
|
|
42
|
+
lastPosition = 0;
|
|
43
|
+
/** When the player was created [Timestamp in Ms] (from lavalink) */
|
|
44
|
+
createdTimeStamp;
|
|
45
|
+
/** The Player Connection's State (from Lavalink) */
|
|
46
|
+
connected = false;
|
|
47
|
+
/** Voice Server Data (from Lavalink) */
|
|
48
|
+
voice = {
|
|
49
|
+
endpoint: null,
|
|
50
|
+
sessionId: null,
|
|
51
|
+
token: null
|
|
52
|
+
};
|
|
53
|
+
data = {};
|
|
54
|
+
/**
|
|
55
|
+
* Create a new Player
|
|
56
|
+
* @param options
|
|
57
|
+
* @param LavalinkManager
|
|
58
|
+
*/
|
|
59
|
+
constructor(options, LavalinkManager) {
|
|
60
|
+
this.options = options;
|
|
61
|
+
this.filterManager = new FilterManager(this);
|
|
62
|
+
this.LavalinkManager = LavalinkManager;
|
|
63
|
+
this.guildId = this.options.guildId;
|
|
64
|
+
this.voiceChannelId = this.options.voiceChannelId;
|
|
65
|
+
this.textChannelId = this.options.textChannelId || null;
|
|
66
|
+
this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
|
|
67
|
+
if (!this.node)
|
|
68
|
+
throw new Error("No available Node was found, please add a LavalinkNode to the Manager via Manager.NodeManager#createNode");
|
|
69
|
+
if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
|
|
70
|
+
this.volume *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
71
|
+
this.LavalinkManager.emit("playerCreate", this);
|
|
72
|
+
if (typeof options.volume === "number" && !isNaN(options.volume))
|
|
73
|
+
this.setVolume(options.volume);
|
|
74
|
+
this.queue = new Queue(this.guildId, {}, new QueueSaver(this.LavalinkManager.options.queueStore, this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueChangesWatcher);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Set custom data.
|
|
78
|
+
* @param key
|
|
79
|
+
* @param value
|
|
80
|
+
*/
|
|
81
|
+
set(key, value) {
|
|
82
|
+
this.data[key] = value;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get custom data.
|
|
87
|
+
* @param key
|
|
88
|
+
*/
|
|
89
|
+
get(key) {
|
|
90
|
+
return this.data[key];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* CLears all the custom data.
|
|
94
|
+
*/
|
|
95
|
+
clearData() {
|
|
96
|
+
const toKeep = Object.keys(this.data).filter(v => v.startsWith("internal_"));
|
|
97
|
+
for (const key in this.data) {
|
|
98
|
+
if (toKeep.includes(key))
|
|
99
|
+
continue;
|
|
100
|
+
delete this.data[key];
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get all custom Data
|
|
106
|
+
*/
|
|
107
|
+
getAllData() {
|
|
108
|
+
return Object.fromEntries(Object.entries(this.data).filter(v => !v[0].startsWith("internal_")));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Play the next track from the queue / a specific track, with playoptions for Lavalink
|
|
112
|
+
* @param options
|
|
113
|
+
*/
|
|
114
|
+
async play(options) {
|
|
115
|
+
if (this.get("internal_queueempty")) {
|
|
116
|
+
clearTimeout(this.get("internal_queueempty"));
|
|
117
|
+
this.set("internal_queueempty", undefined);
|
|
118
|
+
}
|
|
119
|
+
if (options?.track && this.LavalinkManager.utils.isTrack(options?.track)) {
|
|
120
|
+
await this.queue.add(options?.track, 0);
|
|
121
|
+
await queueTrackEnd(this.queue, this.repeatMode === "queue");
|
|
122
|
+
}
|
|
123
|
+
if (!this.queue.current && this.queue.tracks.length)
|
|
124
|
+
await queueTrackEnd(this.queue, this.repeatMode === "queue");
|
|
125
|
+
const track = this.queue.current;
|
|
126
|
+
if (!track)
|
|
127
|
+
throw new Error(`There is no Track in the Queue, nor provided in the PlayOptions`);
|
|
128
|
+
if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
|
|
129
|
+
this.volume = Math.max(Math.min(options?.volume, 500), 0);
|
|
130
|
+
let vol = Number(this.volume);
|
|
131
|
+
if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
|
|
132
|
+
vol *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
133
|
+
this.lavalinkVolume = Math.floor(vol * 100) / 100;
|
|
134
|
+
options.volume = vol;
|
|
135
|
+
}
|
|
136
|
+
const finalOptions = {
|
|
137
|
+
encodedTrack: track.encoded,
|
|
138
|
+
volume: this.lavalinkVolume,
|
|
139
|
+
position: 0,
|
|
140
|
+
...options,
|
|
141
|
+
};
|
|
142
|
+
if ("track" in finalOptions)
|
|
143
|
+
delete finalOptions.track;
|
|
144
|
+
if ((typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position)) || (typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= track.info.duration)))
|
|
145
|
+
throw new Error("PlayerOption#position must be a positive number, less than track's duration");
|
|
146
|
+
if ((typeof finalOptions.volume !== "undefined" && isNaN(finalOptions.volume) || (typeof finalOptions.volume === "number" && finalOptions.volume < 0)))
|
|
147
|
+
throw new Error("PlayerOption#volume must be a positive number");
|
|
148
|
+
if ((typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime)) || (typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= track.info.duration)))
|
|
149
|
+
throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
|
|
150
|
+
if (typeof finalOptions.position === "number" && typeof finalOptions.endTime === "number" && finalOptions.endTime < finalOptions.position)
|
|
151
|
+
throw new Error("PlayerOption#endTime must be bigger than PlayerOption#position");
|
|
152
|
+
if ("noReplace" in finalOptions)
|
|
153
|
+
delete finalOptions.noReplace;
|
|
154
|
+
const now = performance.now();
|
|
155
|
+
await this.node.updatePlayer({
|
|
156
|
+
guildId: this.guildId,
|
|
157
|
+
noReplace: options?.noReplace ?? false,
|
|
158
|
+
playerOptions: finalOptions,
|
|
159
|
+
});
|
|
160
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Set the Volume for the Player
|
|
164
|
+
* @param volume The Volume in percent
|
|
165
|
+
* @param ignoreVolumeDecrementer If it should ignore the volumedecrementer option
|
|
166
|
+
*/
|
|
167
|
+
async setVolume(volume, ignoreVolumeDecrementer = false) {
|
|
168
|
+
volume = Number(volume);
|
|
169
|
+
if (isNaN(volume))
|
|
170
|
+
throw new TypeError("Volume must be a number.");
|
|
171
|
+
this.volume = Math.max(Math.min(volume, 500), 0);
|
|
172
|
+
volume = Number(this.volume);
|
|
173
|
+
if (this.LavalinkManager.options.playerOptions.volumeDecrementer && !ignoreVolumeDecrementer)
|
|
174
|
+
volume *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
175
|
+
this.lavalinkVolume = Math.floor(volume * 100) / 100;
|
|
176
|
+
const now = performance.now();
|
|
177
|
+
if (this.LavalinkManager.options.playerOptions.applyVolumeAsFilter) {
|
|
178
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { filters: { volume: volume / 100 } } });
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { volume } });
|
|
182
|
+
}
|
|
183
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
*
|
|
188
|
+
* @param query Query for your data
|
|
189
|
+
* @param requestUser
|
|
190
|
+
*/
|
|
191
|
+
async search(query, requestUser) {
|
|
192
|
+
// transform the query object
|
|
193
|
+
const Query = {
|
|
194
|
+
query: typeof query === "string" ? query : query.query,
|
|
195
|
+
source: DefaultSources[(typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform] ?? (typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform
|
|
196
|
+
};
|
|
197
|
+
// if user does player.search("ytsearch:Hello")
|
|
198
|
+
const foundSource = [...Object.keys(DefaultSources)].find(source => Query.query.startsWith(`${source}:`));
|
|
199
|
+
if (foundSource && DefaultSources[foundSource]) {
|
|
200
|
+
Query.source = DefaultSources[foundSource]; // set the source to ytsearch:
|
|
201
|
+
Query.query = Query.query.replace(`${foundSource}:`, ""); // remove ytsearch: from the query
|
|
202
|
+
}
|
|
203
|
+
// request the data
|
|
204
|
+
const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}`);
|
|
205
|
+
// transform the data which can be Error, Track or Track[] to enfore [Track]
|
|
206
|
+
const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
|
|
207
|
+
return {
|
|
208
|
+
loadType: res.loadType,
|
|
209
|
+
exception: res.loadType === "error" ? res.data : null,
|
|
210
|
+
pluginInfo: res.pluginInfo || {},
|
|
211
|
+
playlist: res.loadType === "playlist" ? {
|
|
212
|
+
title: res.data.info?.name || res.data.pluginInfo?.name || null,
|
|
213
|
+
author: res.data.info?.author || res.data.pluginInfo?.author || null,
|
|
214
|
+
thumbnail: (res.data.info?.artworkUrl) || (res.data.pluginInfo?.artworkUrl) || ((typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1) ? null : resTracks[res.data?.info?.selectedTrack] ? (resTracks[res.data?.info?.selectedTrack]?.info?.artworkUrl || resTracks[res.data?.info?.selectedTrack]?.info?.pluginInfo?.artworkUrl) : null) || null,
|
|
215
|
+
uri: res.data.info?.url || res.data.info?.uri || res.data.info?.link || res.data.pluginInfo?.url || res.data.pluginInfo?.uri || res.data.pluginInfo?.link || null,
|
|
216
|
+
selectedTrack: typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1 ? null : resTracks[res.data?.info?.selectedTrack] ? this.LavalinkManager.utils.buildTrack(resTracks[res.data?.info?.selectedTrack], requestUser) : null,
|
|
217
|
+
duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || 0), 0) : 0,
|
|
218
|
+
} : null,
|
|
219
|
+
tracks: resTracks.length ? resTracks.map(t => this.LavalinkManager.utils.buildTrack(t, requestUser)) : []
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Pause the player
|
|
224
|
+
*/
|
|
225
|
+
async pause() {
|
|
226
|
+
if (this.paused && !this.playing)
|
|
227
|
+
throw new Error("Player is already paused - not able to pause.");
|
|
228
|
+
this.paused = true;
|
|
229
|
+
const now = performance.now();
|
|
230
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
|
|
231
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Resume the Player
|
|
236
|
+
*/
|
|
237
|
+
async resume() {
|
|
238
|
+
if (!this.paused)
|
|
239
|
+
throw new Error("Player isn't paused - not able to resume.");
|
|
240
|
+
this.paused = false;
|
|
241
|
+
const now = performance.now();
|
|
242
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: false } });
|
|
243
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Seek to a specific Position
|
|
248
|
+
* @param position
|
|
249
|
+
*/
|
|
250
|
+
async seek(position) {
|
|
251
|
+
if (!this.queue.current)
|
|
252
|
+
return undefined;
|
|
253
|
+
position = Number(position);
|
|
254
|
+
if (isNaN(position))
|
|
255
|
+
throw new RangeError("Position must be a number.");
|
|
256
|
+
if (!this.queue.current.info.isSeekable || this.queue.current.info.isStream)
|
|
257
|
+
throw new RangeError("Current Track is not seekable / a stream");
|
|
258
|
+
if (position < 0 || position > this.queue.current.info.duration)
|
|
259
|
+
position = Math.max(Math.min(position, this.queue.current.info.duration), 0);
|
|
260
|
+
this.position = position;
|
|
261
|
+
this.lastPosition = position;
|
|
262
|
+
const now = performance.now();
|
|
263
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { position } });
|
|
264
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Set the Repeatmode of the Player
|
|
269
|
+
* @param repeatMode
|
|
270
|
+
*/
|
|
271
|
+
async setRepeatMode(repeatMode) {
|
|
272
|
+
if (!["off", "track", "queue"].includes(repeatMode))
|
|
273
|
+
throw new RangeError("Repeatmode must be either 'off', 'track', or 'queue'");
|
|
274
|
+
this.repeatMode = repeatMode;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Skip the current song, or a specific amount of songs
|
|
279
|
+
* @param amount provide the index of the next track to skip to
|
|
280
|
+
*/
|
|
281
|
+
async skip(skipTo = 0) {
|
|
282
|
+
if (!this.queue.tracks.length)
|
|
283
|
+
throw new RangeError("Can't skip more than the queue size");
|
|
284
|
+
if (typeof skipTo === "number" && skipTo > 1) {
|
|
285
|
+
if (skipTo > this.queue.tracks.length)
|
|
286
|
+
throw new RangeError("Can't skip more than the queue size");
|
|
287
|
+
await this.queue.splice(0, skipTo - 1);
|
|
288
|
+
}
|
|
289
|
+
if (!this.playing)
|
|
290
|
+
return await this.play();
|
|
291
|
+
const now = performance.now();
|
|
292
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { encodedTrack: null } });
|
|
293
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Connects the Player to the Voice Channel
|
|
298
|
+
* @returns
|
|
299
|
+
*/
|
|
300
|
+
async connect() {
|
|
301
|
+
if (!this.options.voiceChannelId)
|
|
302
|
+
throw new RangeError("No Voice Channel id has been set.");
|
|
303
|
+
await this.LavalinkManager.options.sendToShard(this.guildId, {
|
|
304
|
+
op: 4,
|
|
305
|
+
d: {
|
|
306
|
+
guild_id: this.guildId,
|
|
307
|
+
channel_id: this.options.voiceChannelId,
|
|
308
|
+
self_mute: this.options.selfMute ?? false,
|
|
309
|
+
self_deaf: this.options.selfDeaf ?? true,
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Disconnects the Player from the Voice Channel, but keeps the player in the cache
|
|
316
|
+
* @param force If false it throws an error, if player thinks it's already disconnected
|
|
317
|
+
* @returns
|
|
318
|
+
*/
|
|
319
|
+
async disconnect(force = false) {
|
|
320
|
+
if (!force && !this.options.voiceChannelId)
|
|
321
|
+
throw new RangeError("No Voice Channel id has been set.");
|
|
322
|
+
await this.LavalinkManager.options.sendToShard(this.guildId, {
|
|
323
|
+
op: 4,
|
|
324
|
+
d: {
|
|
325
|
+
guild_id: this.guildId,
|
|
326
|
+
channel_id: null,
|
|
327
|
+
self_mute: false,
|
|
328
|
+
self_deaf: false,
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
this.voiceChannelId = null;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Destroy the player and disconnect from the voice channel
|
|
336
|
+
*/
|
|
337
|
+
async destroy(reason) {
|
|
338
|
+
await this.disconnect(true);
|
|
339
|
+
await this.queue.utils.destroy();
|
|
340
|
+
this.LavalinkManager.deletePlayer(this.guildId);
|
|
341
|
+
await this.node.destroyPlayer(this.guildId);
|
|
342
|
+
this.LavalinkManager.emit("playerDestroy", this, reason);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Move the player on a different Audio-Node
|
|
347
|
+
* @param newNode New Node / New Node Id
|
|
348
|
+
*/
|
|
349
|
+
async changeNode(newNode) {
|
|
350
|
+
const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
|
|
351
|
+
if (!updateNode)
|
|
352
|
+
throw new Error("Could not find the new Node");
|
|
353
|
+
const data = this.toJSON();
|
|
354
|
+
await this.node.destroyPlayer(this.guildId);
|
|
355
|
+
this.node = updateNode;
|
|
356
|
+
await this.connect();
|
|
357
|
+
const now = performance.now();
|
|
358
|
+
await this.node.updatePlayer({
|
|
359
|
+
guildId: this.guildId,
|
|
360
|
+
noReplace: false,
|
|
361
|
+
playerOptions: {
|
|
362
|
+
position: data.position,
|
|
363
|
+
volume: data.volume,
|
|
364
|
+
paused: data.paused,
|
|
365
|
+
filters: { ...data.filters, equalizer: data.equalizer },
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
369
|
+
return this.node.id;
|
|
370
|
+
}
|
|
371
|
+
/** Converts the Player including Queue to a Json state */
|
|
372
|
+
toJSON() {
|
|
373
|
+
return {
|
|
374
|
+
guildId: this.guildId,
|
|
375
|
+
voiceChannelId: this.voiceChannelId,
|
|
376
|
+
textChannelId: this.textChannelId,
|
|
377
|
+
position: this.position,
|
|
378
|
+
lastPosition: this.lastPosition,
|
|
379
|
+
volume: this.volume,
|
|
380
|
+
lavalinkVolume: this.lavalinkVolume,
|
|
381
|
+
repeatMode: this.repeatMode,
|
|
382
|
+
paused: this.paused,
|
|
383
|
+
playing: this.playing,
|
|
384
|
+
createdTimeStamp: this.createdTimeStamp,
|
|
385
|
+
filters: this.filterManager?.data || {},
|
|
386
|
+
equalizer: this.filterManager?.equalizerBands || [],
|
|
387
|
+
queue: this.queue?.utils?.getStored?.() || { current: null, tracks: [], previous: [] },
|
|
388
|
+
nodeId: this.node?.id,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Track } from "./Track";
|
|
2
|
+
export interface StoredQueue {
|
|
3
|
+
current: Track | null;
|
|
4
|
+
previous: Track[];
|
|
5
|
+
tracks: Track[];
|
|
6
|
+
}
|
|
7
|
+
export interface StoreManager extends Record<any, any> {
|
|
8
|
+
/** @async get a Value (MUST RETURN UNPARSED!) */
|
|
9
|
+
get: (guildId: unknown) => Promise<any>;
|
|
10
|
+
/** @async Set a value inside a guildId (MUST BE UNPARSED) */
|
|
11
|
+
set: (guildId: unknown, value: unknown) => Promise<any>;
|
|
12
|
+
/** @async Delete a Database Value based of it's guildId */
|
|
13
|
+
delete: (guildId: unknown) => Promise<any>;
|
|
14
|
+
/** @async Transform the value(s) inside of the StoreManager (IF YOU DON'T NEED PARSING/STRINGIFY, then just return the value) */
|
|
15
|
+
stringify: (value: unknown) => Promise<any>;
|
|
16
|
+
/** @async Parse the saved value back to the Queue (IF YOU DON'T NEED PARSING/STRINGIFY, then just return the value) */
|
|
17
|
+
parse: (value: unknown) => Promise<Partial<StoredQueue>>;
|
|
18
|
+
}
|
|
19
|
+
export interface QueueSaverOptions {
|
|
20
|
+
maxPreviousTracks: number;
|
|
21
|
+
}
|
|
22
|
+
export interface QueueSaver {
|
|
23
|
+
/** @private */
|
|
24
|
+
_: StoreManager;
|
|
25
|
+
/** @private */
|
|
26
|
+
options: QueueSaverOptions;
|
|
27
|
+
}
|
|
28
|
+
export declare class QueueSaver {
|
|
29
|
+
constructor(storeManager: StoreManager, options: QueueSaverOptions);
|
|
30
|
+
get(guildId: string): Promise<Partial<StoredQueue>>;
|
|
31
|
+
delete(guildId: string): Promise<any>;
|
|
32
|
+
set(guildId: string, value: any): Promise<any>;
|
|
33
|
+
sync(guildId: string): Promise<Partial<StoredQueue>>;
|
|
34
|
+
}
|
|
35
|
+
export declare class DefaultQueueStore {
|
|
36
|
+
private data;
|
|
37
|
+
constructor();
|
|
38
|
+
get(guildId: any): Promise<any>;
|
|
39
|
+
set(guildId: any, stringifiedValue: any): Promise<Map<any, any>>;
|
|
40
|
+
delete(guildId: any): Promise<boolean>;
|
|
41
|
+
stringify(value: any): Promise<any>;
|
|
42
|
+
parse(value: any): Promise<Partial<StoredQueue>>;
|
|
43
|
+
}
|
|
44
|
+
export declare class QueueChangesWatcher {
|
|
45
|
+
constructor();
|
|
46
|
+
tracksAdd(guildId: string, tracks: Track[], position: number, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue): void;
|
|
47
|
+
tracksRemoved(guildId: string, tracks: Track[], position: number, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue): void;
|
|
48
|
+
shuffled(guildId: string, oldStoredQueue: StoredQueue, newStoredQueue: StoredQueue): void;
|
|
49
|
+
}
|
|
50
|
+
export declare class Queue {
|
|
51
|
+
readonly tracks: Track[];
|
|
52
|
+
readonly previous: Track[];
|
|
53
|
+
current: Track | null;
|
|
54
|
+
options: {
|
|
55
|
+
maxPreviousTracks: number;
|
|
56
|
+
};
|
|
57
|
+
private readonly guildId;
|
|
58
|
+
protected readonly QueueSaver: QueueSaver | null;
|
|
59
|
+
static readonly StaticSymbol: Symbol;
|
|
60
|
+
private managerUtils;
|
|
61
|
+
private queueChanges;
|
|
62
|
+
constructor(guildId: string, data?: Partial<StoredQueue>, QueueSaver?: QueueSaver, queueChangesWatcher?: QueueChangesWatcher);
|
|
63
|
+
/**
|
|
64
|
+
* Utils for a Queue
|
|
65
|
+
*/
|
|
66
|
+
utils: {
|
|
67
|
+
/**
|
|
68
|
+
* Save the current cached Queue on the database/server (overides the server)
|
|
69
|
+
*/
|
|
70
|
+
save: () => Promise<any>;
|
|
71
|
+
/**
|
|
72
|
+
* Sync the current queue database/server with the cached one
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
75
|
+
sync: (override?: boolean, dontSyncCurrent?: boolean) => Promise<void>;
|
|
76
|
+
destroy: () => Promise<any>;
|
|
77
|
+
/**
|
|
78
|
+
* @returns {{current:Track|null, previous:Track[], tracks:Track[]}}The Queue, but in a raw State, which allows easier handling for the storeManager
|
|
79
|
+
*/
|
|
80
|
+
getStored: () => StoredQueue;
|
|
81
|
+
/**
|
|
82
|
+
* Get the Total Duration of the Queue-Songs summed up
|
|
83
|
+
* @returns {number}
|
|
84
|
+
*/
|
|
85
|
+
totalDuration: () => number;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Shuffles the current Queue, then saves it
|
|
89
|
+
* @returns Amount of Tracks in the Queue
|
|
90
|
+
*/
|
|
91
|
+
shuffle(): Promise<number>;
|
|
92
|
+
/**
|
|
93
|
+
* Add a Track to the Queue, and after saved in the "db" it returns the amount of the Tracks
|
|
94
|
+
* @param {Track | Track[]} TrackOrTracks
|
|
95
|
+
* @param {number} index At what position to add the Track
|
|
96
|
+
* @returns {number} Queue-Size (for the next Tracks)
|
|
97
|
+
*/
|
|
98
|
+
add(TrackOrTracks: Track | Track[], index?: number): any;
|
|
99
|
+
/**
|
|
100
|
+
* Splice the tracks in the Queue
|
|
101
|
+
* @param {number} index Where to remove the Track
|
|
102
|
+
* @param {number} amount How many Tracks to remove?
|
|
103
|
+
* @param {Track | Track[]} TrackOrTracks Want to Add more Tracks?
|
|
104
|
+
* @returns {Track} Spliced Track
|
|
105
|
+
*/
|
|
106
|
+
splice(index: number, amount: number, TrackOrTracks?: Track | Track[]): any;
|
|
107
|
+
}
|