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.
Files changed (76) hide show
  1. package/README.md +103 -152
  2. package/dist/cjs/index.d.ts +9 -0
  3. package/dist/cjs/index.js +12 -0
  4. package/dist/cjs/structures/Filters.d.ts +231 -0
  5. package/dist/cjs/structures/Filters.js +481 -0
  6. package/dist/cjs/structures/LavalinkManager.d.ts +124 -0
  7. package/dist/cjs/structures/LavalinkManager.js +168 -0
  8. package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
  9. package/dist/cjs/structures/LavalinkManagerStatics.js +84 -0
  10. package/dist/cjs/structures/Node.d.ts +245 -0
  11. package/dist/cjs/structures/Node.js +602 -0
  12. package/dist/cjs/structures/NodeManager.d.ts +61 -0
  13. package/dist/cjs/structures/NodeManager.js +35 -0
  14. package/dist/cjs/structures/Player.d.ts +191 -0
  15. package/dist/cjs/structures/Player.js +395 -0
  16. package/dist/cjs/structures/Queue.d.ts +107 -0
  17. package/dist/cjs/structures/Queue.js +215 -0
  18. package/dist/cjs/structures/Track.d.ts +47 -0
  19. package/dist/cjs/structures/Track.js +2 -0
  20. package/dist/cjs/structures/Utils.d.ts +258 -0
  21. package/dist/cjs/structures/Utils.js +179 -0
  22. package/dist/esm/index.d.ts +9 -0
  23. package/dist/esm/index.js +9 -0
  24. package/dist/esm/structures/Filters.d.ts +231 -0
  25. package/dist/esm/structures/Filters.js +477 -0
  26. package/dist/esm/structures/LavalinkManager.d.ts +124 -0
  27. package/dist/esm/structures/LavalinkManager.js +164 -0
  28. package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
  29. package/dist/esm/structures/LavalinkManagerStatics.js +81 -0
  30. package/dist/esm/structures/Node.d.ts +245 -0
  31. package/dist/esm/structures/Node.js +597 -0
  32. package/dist/esm/structures/NodeManager.d.ts +61 -0
  33. package/dist/esm/structures/NodeManager.js +31 -0
  34. package/dist/esm/structures/Player.d.ts +191 -0
  35. package/dist/esm/structures/Player.js +391 -0
  36. package/dist/esm/structures/Queue.d.ts +107 -0
  37. package/dist/esm/structures/Queue.js +208 -0
  38. package/dist/esm/structures/Track.d.ts +47 -0
  39. package/dist/esm/structures/Track.js +1 -0
  40. package/dist/esm/structures/Utils.d.ts +258 -0
  41. package/dist/esm/structures/Utils.js +173 -0
  42. package/dist/index.d.ts +10 -0
  43. package/dist/index.js +13 -0
  44. package/dist/structures/Filters.d.ts +230 -0
  45. package/dist/structures/Filters.js +472 -0
  46. package/dist/structures/LavalinkManager.d.ts +47 -0
  47. package/dist/structures/LavalinkManager.js +36 -0
  48. package/dist/structures/LavalinkManagerStatics.d.ts +3 -0
  49. package/dist/structures/LavalinkManagerStatics.js +76 -0
  50. package/dist/structures/Node.d.ts +171 -0
  51. package/dist/structures/Node.js +462 -0
  52. package/dist/structures/NodeManager.d.ts +58 -0
  53. package/dist/structures/NodeManager.js +25 -0
  54. package/dist/structures/Player.d.ts +101 -0
  55. package/dist/structures/Player.js +232 -0
  56. package/dist/structures/PlayerManager.d.ts +62 -0
  57. package/dist/structures/PlayerManager.js +26 -0
  58. package/dist/structures/Queue.d.ts +93 -0
  59. package/dist/structures/Queue.js +160 -0
  60. package/dist/structures/QueueManager.d.ts +77 -0
  61. package/dist/structures/QueueManager.js +74 -0
  62. package/dist/structures/Track.d.ts +27 -0
  63. package/dist/structures/Track.js +2 -0
  64. package/dist/structures/Utils.d.ts +183 -0
  65. package/dist/structures/Utils.js +43 -0
  66. package/dist/types/index.d.ts +9 -0
  67. package/dist/types/structures/Filters.d.ts +231 -0
  68. package/dist/types/structures/LavalinkManager.d.ts +124 -0
  69. package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
  70. package/dist/types/structures/Node.d.ts +245 -0
  71. package/dist/types/structures/NodeManager.d.ts +61 -0
  72. package/dist/types/structures/Player.d.ts +191 -0
  73. package/dist/types/structures/Queue.d.ts +107 -0
  74. package/dist/types/structures/Track.d.ts +47 -0
  75. package/dist/types/structures/Utils.d.ts +258 -0
  76. 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
+ }