aqualink 2.0.0 → 2.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 CHANGED
@@ -4,7 +4,8 @@ An Stable, performant, Recourse friendly and fast lavalink wrapper
4
4
  This code is based in riffy, but its an 100% Rewrite made from scratch...
5
5
 
6
6
  # Why use AquaLink
7
- - Dependecy-free (0 dependencys), may change in future
7
+ - Only uses 1 dependecy (ws for websocket, atm)
8
+ - HTTP1.1 / HTTP2 Support
8
9
  - Very Low memory comsuption
9
10
  - Built in Queue manager
10
11
  - Lots of features to use
@@ -22,15 +23,19 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
22
23
  - https://github.com/DRSchlaubi/lyrics.kt (?)
23
24
  - https://github.com/DuncteBot/java-timed-lyrics (RECOMMENDED)
24
25
 
25
- # Tralalero Tralala 2.0.0 Released
26
+ # Tralalero Tralala 2.0.2 Released
26
27
  ---
27
-
28
- ### Now aqualink is 100% depedency free!
29
- - Rewrite the rest to use https / http
30
- - Removed undici usage
31
- - Rewrite NODE to use built-in WebSocket (no need to upgrade to node 20 or more!)
32
- - Uses my own made websocket system, so please report any bugs (i beg)
33
- - more stuff soon, now bye
28
+ - Updated the `PLAYER` module
29
+ - Now uses Set() and WeakMap()
30
+ - More simple logic
31
+ - Improved error handling, and also a bit of memory management
32
+ - Rewrite the shuffle() method to use Fisher-Yates shuffle
33
+ - Fix updatePlayer sending an extra pause request
34
+ - Improved the clean up system (correctly uses clearData now)
35
+
36
+ - Updated the `NODE` module
37
+ - Optimized stats creating / reusing
38
+ - Some stuff i made on the reconnecting system
34
39
 
35
40
  # Docs (Wiki)
36
41
  - https://github.com/ToddyTheNoobDud/AquaLink/wiki
@@ -1,19 +1,14 @@
1
- const { request } = require("undici");
2
-
1
+ const http2 = require('http2');
3
2
  const sourceHandlers = new Map([
4
3
  ['spotify', uri => fetchThumbnail(`https://open.spotify.com/oembed?url=${uri}`)],
5
4
  ['youtube', identifier => fetchYouTubeThumbnail(identifier)]
6
5
  ]);
7
-
8
6
  const YOUTUBE_URL_TEMPLATE = (quality) => (id) => `https://img.youtube.com/vi/${id}/${quality}.jpg`;
9
7
  const YOUTUBE_QUALITIES = ['maxresdefault', 'hqdefault', 'mqdefault', 'default'].map(YOUTUBE_URL_TEMPLATE);
10
-
11
8
  async function getImageUrl(info) {
12
9
  if (!info?.sourceName || !info?.uri) return null;
13
-
14
10
  const handler = sourceHandlers.get(info.sourceName.toLowerCase());
15
11
  if (!handler) return null;
16
-
17
12
  try {
18
13
  return await handler(info.uri);
19
14
  } catch (error) {
@@ -21,28 +16,42 @@ async function getImageUrl(info) {
21
16
  return null;
22
17
  }
23
18
  }
24
-
25
- async function fetchThumbnail(url) {
26
- try {
27
- const { body } = await request(url, {
28
- method: "GET",
29
- headers: { 'Accept': 'application/json' }
19
+ function fetchThumbnail(url) {
20
+ return new Promise((resolve, reject) => {
21
+ const client = http2.connect(url);
22
+
23
+ const req = client.request({ ':path': '/' });
24
+
25
+ let data = '';
26
+
27
+ req.on('response', (headers, flags) => {
28
+ if (headers[':status'] !== 200) {
29
+ return reject(`Failed to fetch: ${headers[':status']}`);
30
+ }
30
31
  });
31
- const json = await body.json();
32
- return json.thumbnail_url || null;
33
- } catch (error) {
34
- console.error(`Error fetching thumbnail from ${url}:`, error);
35
- return null;
36
- }
32
+ req.on('data', chunk => {
33
+ data += chunk;
34
+ });
35
+ req.on('end', () => {
36
+ try {
37
+ const json = JSON.parse(data);
38
+ resolve(json.thumbnail_url || null);
39
+ } catch (error) {
40
+ reject(`JSON parse error: ${error.message}`);
41
+ } finally {
42
+ client.close();
43
+ }
44
+ });
45
+ req.on('error', (error) => {
46
+ reject(`Request error: ${error.message}`);
47
+ client.close();
48
+ });
49
+ req.end();
50
+ });
37
51
  }
38
-
39
52
  async function fetchYouTubeThumbnail(identifier) {
40
- return Promise.race(
41
- YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)))
42
- ).catch(() => {
43
- console.error('No valid YouTube thumbnail found.');
44
- return null;
45
- });
53
+ const promises = YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)));
54
+ const results = await Promise.race(promises);
55
+ return results || null;
46
56
  }
47
-
48
- module.exports = { getImageUrl };
57
+ module.exports = { getImageUrl };
package/build/index.d.ts CHANGED
@@ -1,80 +1,352 @@
1
- declare module "AquaLink" {
2
- export class Aqua {
3
- /**
4
- * @param {Object} client - The client instance.
5
- * @param {Array<Object>} nodes - An array of node configurations.
6
- * @param {Object} options - Configuration options for Aqua.
7
- * @param {Function} options.send - Function to send data.
8
- * @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
9
- * @param {string} [options.restVersion="v4"] - Version of the REST API.
10
- * @param {Array<Object>} [options.plugins=[]] - Plugins to load.
11
- * @param {boolean} [options.autoResume=false] - Automatically resume tracks on reconnect.
12
- * @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely.
13
- */
14
- constructor(client: any, nodes: Array<any>, options?: { [key: string]: any });
1
+ declare module "aqualink" {
2
+ import { EventEmitter } from "events";
3
+
4
+ export class Aqua extends EventEmitter {
5
+ constructor(client: any, nodes: Array<NodeConfig>, options?: AquaOptions);
15
6
  init(clientId: string): this;
16
- createNode(options: { [key: string]: any }): Node;
17
- createPlayer(node: Node, options: { [key: string]: any }): Player;
7
+ createNode(options: NodeConfig): Node;
8
+ createPlayer(node: Node, options: PlayerOptions): Player;
18
9
  destroyPlayer(guildId: string): Promise<void>;
19
- resolve(options: { query: string, source?: string, requester?: any, nodes?: any }): Promise<any>;
20
- updateVoiceState(data: { d: any, t: string }): void;
10
+ resolve(options: ResolveOptions): Promise<ResolveResponse>;
11
+ updateVoiceState(data: VoiceStateUpdate): void;
12
+ getOption<T>(options: Record<string, any>, key: string, defaultValue: T): T;
13
+ defaultSendFunction(payload: any): void;
14
+ validateInputs(client: any, nodes: Array<NodeConfig>): void;
15
+ get leastUsedNodes(): Node[];
16
+ fetchRegion(region: string): Node[];
17
+ calculateLoad(node: Node): number;
18
+ createConnection(options: PlayerOptions): Player;
19
+ getRequestNode(nodes?: string | Node): Node;
20
+ ensureInitialized(): void;
21
+ formatQuery(query: string, source: string): string;
22
+ handleNoMatches(rest: Rest, query: string): Promise<ResolveResponse>;
23
+ constructorResponse(response: any, requester: any, requestNode: Node): ResolveResponse;
24
+ get(guildId: string): Player;
25
+ cleanupPlayer(player: Player): void;
26
+
27
+ client: any;
28
+ nodes: Array<NodeConfig>;
29
+ nodeMap: Map<string, Node>;
30
+ players: Map<string, Player>;
31
+ clientId: string | null;
32
+ initiated: boolean;
33
+ options: AquaOptions;
34
+ shouldDeleteMessage: boolean;
35
+ defaultSearchPlatform: string;
36
+ leaveOnEnd: boolean;
37
+ restVersion: string;
38
+ plugins: Array<Plugin>;
39
+ version: string;
40
+ send: (payload: any) => void;
41
+ autoResume: boolean;
42
+ infiniteReconnects: boolean;
43
+ _leastUsedCache: { nodes: Node[], timestamp: number };
44
+ }
45
+
46
+ export interface AquaOptions {
47
+ send?: (payload: any) => void;
48
+ defaultSearchPlatform?: string;
49
+ restVersion?: string;
50
+ plugins?: Array<Plugin>;
51
+ autoResume?: boolean;
52
+ infiniteReconnects?: boolean;
53
+ shouldDeleteMessage?: boolean;
54
+ leaveOnEnd?: boolean;
55
+ }
56
+
57
+ export interface NodeConfig {
58
+ name?: string;
59
+ host: string;
60
+ port: number;
61
+ password: string;
62
+ secure?: boolean;
63
+ sessionId?: string;
64
+ regions?: string[];
65
+ }
66
+
67
+ export interface PlayerOptions {
68
+ guildId: string;
69
+ textChannel?: string;
70
+ voiceChannel?: string;
71
+ defaultVolume?: number;
72
+ loop?: string;
73
+ shouldDeleteMessage?: boolean;
74
+ leaveOnEnd?: boolean;
75
+ region?: string;
76
+ }
77
+
78
+ export interface ResolveOptions {
79
+ query: string;
80
+ source?: string;
81
+ requester?: any;
82
+ nodes?: string | Node;
83
+ }
84
+
85
+ export interface ResolveResponse {
86
+ loadType: string;
87
+ exception: any;
88
+ playlistInfo: any;
89
+ pluginInfo: any;
90
+ tracks: Track[];
91
+ }
92
+
93
+ export interface VoiceStateUpdate {
94
+ d: any;
95
+ t: string;
21
96
  }
22
97
 
23
98
  export class Connection {
24
99
  constructor(player: Player);
25
100
  setServerUpdate(data: { endpoint: string, token: string }): void;
26
101
  setStateUpdate(data: { channel_id: string, session_id: string, self_deaf: boolean, self_mute: boolean }): void;
102
+ _updatePlayerVoiceData(): Promise<void>;
103
+
104
+ playerRef: WeakRef<Player>;
105
+ voice: { sessionId: string | null, endpoint: string | null, token: string | null };
106
+ region: string | null;
107
+ selfDeaf: boolean;
108
+ selfMute: boolean;
109
+ voiceChannel: string;
110
+ guildId: string;
111
+ aqua: Aqua;
112
+ nodes: any;
27
113
  }
28
114
 
29
115
  export class Filters {
30
- constructor(player: Player, options?: { [key: string]: any });
116
+ constructor(player: Player, options?: FiltersOptions);
31
117
  setEqualizer(bands: Array<any>): Promise<void>;
32
- setKaraoke(enabled: boolean, options?: { [key: string]: any }): Promise<void>;
118
+ setKaraoke(enabled: boolean, options?: FiltersOptions): Promise<void>;
119
+ setTimescale(enabled: boolean, options?: FiltersOptions): Promise<void>;
120
+ setTremolo(enabled: boolean, options?: FiltersOptions): Promise<void>;
121
+ setVibrato(enabled: boolean, options?: FiltersOptions): Promise<void>;
122
+ setRotation(enabled: boolean, options?: FiltersOptions): Promise<void>;
123
+ setDistortion(enabled: boolean, options?: FiltersOptions): Promise<void>;
124
+ setChannelMix(enabled: boolean, options?: FiltersOptions): Promise<void>;
125
+ setLowPass(enabled: boolean, options?: FiltersOptions): Promise<void>;
126
+ setBassboost(enabled: boolean, options?: FiltersOptions): Promise<void>;
127
+ setSlowmode(enabled: boolean, options?: FiltersOptions): Promise<void>;
128
+ setNightcore(enabled: boolean, options?: FiltersOptions): Promise<void>;
129
+ setVaporwave(enabled: boolean, options?: FiltersOptions): Promise<void>;
130
+ set8D(enabled: boolean, options?: FiltersOptions): Promise<void>;
33
131
  clearFilters(): Promise<void>;
132
+ updateFilters(): Promise<void>;
133
+
134
+ player: Player;
135
+ volume: number;
136
+ equalizer: any[];
137
+ karaoke: any;
138
+ timescale: any;
139
+ tremolo: any;
140
+ vibrato: any;
141
+ rotation: any;
142
+ distortion: any;
143
+ channelMix: any;
144
+ lowPass: any;
145
+ bassboost: any;
146
+ slowmode: any;
147
+ nightcore: any;
148
+ vaporwave: any;
149
+ _8d: any;
150
+ }
151
+
152
+ export interface FiltersOptions {
153
+ volume?: number;
154
+ equalizer?: any[];
155
+ karaoke?: any;
156
+ timescale?: any;
157
+ tremolo?: any;
158
+ vibrato?: any;
159
+ rotation?: any;
160
+ distortion?: any;
161
+ channelMix?: any;
162
+ lowPass?: any;
163
+ bassboost?: any;
164
+ slowmode?: any;
165
+ nightcore?: any;
166
+ vaporwave?: any;
167
+ _8d?: any;
34
168
  }
35
169
 
36
170
  export class Node {
37
- constructor(aqua: Aqua, connOptions: { [key: string]: any }, options?: { [key: string]: any });
171
+ constructor(aqua: Aqua, connOptions: NodeConfig, options?: NodeOptions);
38
172
  connect(): Promise<void>;
39
173
  getStats(): Promise<any>;
40
174
  destroy(clean?: boolean): void;
175
+
176
+ aqua: Aqua;
177
+ name: string;
178
+ host: string;
179
+ port: number;
180
+ password: string;
181
+ secure: boolean;
182
+ sessionId: string | null;
183
+ regions: string[];
184
+ wsUrl: URL;
185
+ rest: Rest;
186
+ resumeTimeout: number;
187
+ autoResume: boolean;
188
+ reconnectTimeout: number;
189
+ reconnectTries: number;
190
+ infiniteReconnects: boolean;
191
+ connected: boolean;
192
+ info: any;
193
+ defaultStats: any;
194
+ stats: any;
195
+ }
196
+
197
+ export interface NodeOptions {
198
+ resumeTimeout?: number;
199
+ autoResume?: boolean;
200
+ reconnectTimeout?: number;
201
+ reconnectTries?: number;
202
+ infiniteReconnects?: boolean;
41
203
  }
42
204
 
43
- export class Player {
44
- constructor(aqua: Aqua, nodes: any, options?: { [key: string]: any });
205
+ export class Player extends EventEmitter {
206
+ constructor(aqua: Aqua, nodes: any, options?: PlayerOptions);
45
207
  play(): Promise<void>;
46
208
  pause(paused: boolean): this;
47
209
  skip(): Promise<void>;
48
210
  destroy(): void;
211
+ connect(options: PlayerOptions): this;
212
+ disconnect(): this;
213
+ setVolume(volume: number): this;
214
+ setLoop(mode: string): this;
215
+ setTextChannel(channel: string): this;
216
+ setVoiceChannel(channel: string): this;
217
+ shuffle(): this;
218
+ replay(): this;
219
+ stop(): this;
220
+ seek(position: number): this;
221
+ searchLyrics(query: string): Promise<any>;
222
+ lyrics(): Promise<any>;
223
+ addToPreviousTrack(track: Track): void;
224
+ updatePlayer(data: any): Promise<void>;
225
+ cleanup(): Promise<void>;
226
+ updateTrackState(playing: boolean, paused: boolean): void;
227
+ handleEvent(payload: any): Promise<void>;
228
+ handleUnknownEvent(payload: any): void;
229
+ trackStart(player: Player, track: Track): Promise<void>;
230
+ trackEnd(player: Player, track: Track, payload: any): Promise<void>;
231
+ trackError(player: Player, track: Track, payload: any): Promise<void>;
232
+ trackStuck(player: Player, track: Track, payload: any): Promise<void>;
233
+ socketClosed(player: Player, payload: any): Promise<void>;
234
+ send(data: any): void;
235
+
236
+ static LOOP_MODES: { NONE: string, TRACK: string, QUEUE: string };
237
+ static EVENT_HANDLERS: { [key: string]: string };
238
+ static validModes: Set<string>;
239
+
240
+ aqua: Aqua;
241
+ nodes: any;
242
+ guildId: string;
243
+ textChannel: string;
244
+ voiceChannel: string;
245
+ connection: Connection;
246
+ filters: Filters;
247
+ volume: number;
248
+ loop: string;
249
+ queue: Queue;
250
+ previousTracks: Track[];
251
+ shouldDeleteMessage: boolean;
252
+ leaveOnEnd: boolean;
253
+ playing: boolean;
254
+ paused: boolean;
255
+ connected: boolean;
256
+ current: Track | null;
257
+ timestamp: number;
258
+ ping: number;
259
+ nowPlayingMessage: any;
260
+ onPlayerUpdate: (state: any) => void;
49
261
  }
50
262
 
51
263
  export class Plugin {
52
264
  constructor(name: string);
53
265
  load(aqua: Aqua): void;
54
266
  unload(aqua: Aqua): void;
267
+
268
+ name: string;
55
269
  }
56
270
 
57
- export class Queue extends Array {
271
+ export class Queue extends Array<any> {
272
+ constructor(...elements: any[]);
273
+ size: number;
274
+ first: any;
275
+ last: any;
58
276
  add(track: any): this;
59
277
  remove(track: any): void;
60
278
  clear(): void;
61
279
  shuffle(): void;
62
280
  peek(): any;
63
- toArray(): Array<any>;
281
+ toArray(): any[];
64
282
  at(index: number): any;
65
283
  dequeue(): any;
66
284
  isEmpty(): boolean;
285
+ enqueue(track: any): this;
67
286
  }
68
287
 
69
288
  export class Rest {
70
- constructor(aqua: Aqua, options: { [key: string]: any });
289
+ constructor(aqua: Aqua, options: RestOptions);
71
290
  makeRequest(method: string, endpoint: string, body?: any): Promise<any>;
72
291
  getPlayers(): Promise<any>;
73
292
  destroyPlayer(guildId: string): Promise<void>;
293
+ getTracks(identifier: string): Promise<any>;
294
+ decodeTrack(track: string): Promise<any>;
295
+ decodeTracks(tracks: any[]): Promise<any>;
296
+ getStats(): Promise<any>;
297
+ getInfo(): Promise<any>;
298
+ getRoutePlannerStatus(): Promise<any>;
299
+ getRoutePlannerAddress(address: string): Promise<any>;
300
+ getLyrics(options: { track: any }): Promise<any>;
301
+ setSessionId(sessionId: string): void;
302
+ buildEndpoint(...segments: string[]): string;
303
+ validateSessionId(): void;
304
+ updatePlayer(options: { guildId: string, data: any }): Promise<void>;
305
+
306
+ aqua: Aqua;
307
+ sessionId: string;
308
+ version: string;
309
+ baseUrl: string;
310
+ headers: Record<string, string>;
311
+ client: any;
312
+ }
313
+
314
+ export interface RestOptions {
315
+ secure: boolean;
316
+ host: string;
317
+ port: number;
318
+ sessionId: string;
319
+ password: string;
74
320
  }
75
321
 
76
322
  export class Track {
77
- constructor(data: { [key: string]: any }, requester: Player, nodes: Node);
323
+ constructor(data: TrackData, requester: Player, nodes: Node);
78
324
  resolve(aqua: Aqua): Promise<Track | null>;
325
+ resolveThumbnail(thumbnail: string): string | null;
326
+ _findMatchingTrack(tracks: Track[]): Track | null;
327
+
328
+ info: TrackInfo;
329
+ track: string | null;
330
+ playlist: any;
331
+ requester: Player;
332
+ nodes: Node;
333
+ }
334
+
335
+ export interface TrackData {
336
+ info?: TrackInfo;
337
+ encoded?: string;
338
+ playlist?: any;
339
+ }
340
+
341
+ export interface TrackInfo {
342
+ identifier: string;
343
+ isSeekable: boolean;
344
+ author: string;
345
+ length: number;
346
+ isStream: boolean;
347
+ title: string;
348
+ uri: string;
349
+ sourceName: string;
350
+ artworkUrl: string;
79
351
  }
80
352
  }
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const WebSocket = require('../handlers/WebSocket');
2
+ const WebSocket = require('ws');
3
3
  const Rest = require("./Rest");
4
4
 
5
5
  class Node {
@@ -37,16 +37,7 @@ class Node {
37
37
  this.infiniteReconnects = options.infiniteReconnects || false;
38
38
  this.connected = false;
39
39
  this.info = null;
40
- this.defaultStats = this.#createDefaultStats();
41
- this.stats = { ...this.defaultStats };
42
- this._onOpen = this.#onOpen.bind(this);
43
- this._onError = this.#onError.bind(this);
44
- this._onMessage = this.#onMessage.bind(this);
45
- this._onClose = this.#onClose.bind(this);
46
- }
47
-
48
- #createDefaultStats() {
49
- return {
40
+ this.stats = {
50
41
  players: 0,
51
42
  playingPlayers: 0,
52
43
  uptime: 0,
@@ -55,8 +46,13 @@ class Node {
55
46
  frameStats: { sent: 0, nulled: 0, deficit: 0 },
56
47
  ping: 0
57
48
  };
49
+ this._onOpen = this.#onOpen.bind(this);
50
+ this._onError = this.#onError.bind(this);
51
+ this._onMessage = this.#onMessage.bind(this);
52
+ this._onClose = this.#onClose.bind(this);
58
53
  }
59
54
 
55
+
60
56
  async connect() {
61
57
  this.#ws = new WebSocket(this.wsUrl.href, {
62
58
  headers: this.#constructHeaders(),
@@ -97,10 +93,8 @@ class Node {
97
93
  }
98
94
 
99
95
  async getStats() {
100
- const stats = await this.rest.getStats();
101
- if (JSON.stringify(this.stats) !== JSON.stringify({ ...this.defaultStats, ...stats })) {
102
- this.stats = { ...this.defaultStats, ...stats };
103
- }
96
+ const newStats = await this.rest.getStats();
97
+ Object.assign(this.stats, newStats);
104
98
  return this.stats;
105
99
  }
106
100
 
@@ -141,48 +135,41 @@ class Node {
141
135
 
142
136
  #updateStats(payload) {
143
137
  if (!payload) return;
144
- const newStats = {
145
- ...this.stats,
146
- ...payload,
147
- memory: this.#updateMemoryStats(payload.memory),
148
- cpu: this.#updateCpuStats(payload.cpu),
149
- frameStats: this.#updateFrameStats(payload.frameStats)
150
- };
151
- if (JSON.stringify(this.stats) !== JSON.stringify(newStats)) {
152
- this.stats = newStats;
153
- }
154
- }
155
138
 
156
- #updateMemoryStats(memory = {}) {
157
- const allocated = memory.allocated || 0;
158
- const free = memory.free || 0;
159
- const used = memory.used || 0;
160
- return {
139
+ this.stats.players = payload.players || this.stats.players;
140
+ this.stats.playingPlayers = payload.playingPlayers || this.stats.playingPlayers;
141
+ this.stats.uptime = payload.uptime || this.stats.uptime;
142
+ this.stats.ping = payload.ping || this.stats.ping;
143
+
144
+ const memory = payload.memory || {};
145
+ const allocated = memory.allocated || this.stats.memory.allocated;
146
+ const free = memory.free || this.stats.memory.free;
147
+ const used = memory.used || this.stats.memory.used;
148
+
149
+ this.stats.memory = {
161
150
  free,
162
151
  used,
163
152
  allocated,
164
- reservable: memory.reservable || 0,
165
- freePercentage: allocated ? (free / allocated) * 100 : 0,
166
- usedPercentage: allocated ? (used / allocated) * 100 : 0
153
+ reservable: memory.reservable || this.stats.memory.reservable,
154
+ freePercentage: allocated ? (free / allocated) * 100 : this.stats.memory.freePercentage,
155
+ usedPercentage: allocated ? (used / allocated) * 100 : this.stats.memory.usedPercentage
167
156
  };
168
- }
169
157
 
170
- #updateCpuStats(cpu = {}) {
171
- const cores = cpu.cores || 0;
172
- return {
158
+ const cpu = payload.cpu || {};
159
+ const cores = cpu.cores || this.stats.cpu.cores;
160
+
161
+ this.stats.cpu = {
173
162
  cores,
174
- systemLoad: cpu.systemLoad || 0,
175
- lavalinkLoad: cpu.lavalinkLoad || 0,
176
- lavalinkLoadPercentage: cores ? (cpu.lavalinkLoad / cores) * 100 : 0
163
+ systemLoad: cpu.systemLoad || this.stats.cpu.systemLoad,
164
+ lavalinkLoad: cpu.lavalinkLoad || this.stats.cpu.lavalinkLoad,
165
+ lavalinkLoadPercentage: cores ? (cpu.lavalinkLoad / cores) * 100 : this.stats.cpu.lavalinkLoadPercentage
177
166
  };
178
- }
179
167
 
180
- #updateFrameStats(frameStats = {}) {
181
- if (!frameStats) return { sent: 0, nulled: 0, deficit: 0 };
182
- return {
183
- sent: frameStats.sent || 0,
184
- nulled: frameStats.nulled || 0,
185
- deficit: frameStats.deficit || 0
168
+ const frameStats = payload.frameStats || {};
169
+ this.stats.frameStats = {
170
+ sent: frameStats.sent || this.stats.frameStats.sent,
171
+ nulled: frameStats.nulled || this.stats.frameStats.nulled,
172
+ deficit: frameStats.deficit || this.stats.frameStats.deficit
186
173
  };
187
174
  }
188
175
 
@@ -11,6 +11,7 @@ class Player extends EventEmitter {
11
11
  TRACK: "track",
12
12
  QUEUE: "queue"
13
13
  });
14
+
14
15
  static EVENT_HANDLERS = Object.freeze({
15
16
  TrackStartEvent: "trackStart",
16
17
  TrackEndEvent: "trackEnd",
@@ -19,7 +20,8 @@ class Player extends EventEmitter {
19
20
  TrackChangeEvent: "trackChange",
20
21
  WebSocketClosedEvent: "socketClosed"
21
22
  });
22
- static validModes = ["none", "track", "queue"]
23
+
24
+ static validModes = new Set(Object.values(Player.LOOP_MODES));
23
25
 
24
26
  constructor(aqua, nodes, options = {}) {
25
27
  super();
@@ -31,7 +33,7 @@ class Player extends EventEmitter {
31
33
  this.connection = new Connection(this);
32
34
  this.filters = new Filters(this);
33
35
  this.volume = Math.min(Math.max(options.defaultVolume ?? 100, 0), 200);
34
- this.loop = Player.LOOP_MODES[options.loop?.toUpperCase()] || Player.LOOP_MODES.NONE;
36
+ this.loop = Player.validModes.has(options.loop) ? options.loop : Player.LOOP_MODES.NONE;
35
37
  this.queue = new Queue();
36
38
  this.previousTracks = [];
37
39
  this.shouldDeleteMessage = options.shouldDeleteMessage ?? false;
@@ -45,23 +47,19 @@ class Player extends EventEmitter {
45
47
  this.ping = 0;
46
48
  this.nowPlayingMessage = null;
47
49
 
48
- this.onPlayerUpdate = ({ state }) => state && Object.assign(this, state) && this.aqua.emit("playerUpdate", this, { state });
49
- this.handleEvent = async (payload) => {
50
- const player = this.aqua.players.get(payload.guildId);
51
- if (!player) return;
50
+ this.on("playerUpdate", ({ state }) => {
51
+ if (state) Object.assign(this, state);
52
+ this.aqua.emit("playerUpdate", this, { state });
53
+ });
54
+
55
+ this.on("event", async (payload) => {
52
56
  const handler = Player.EVENT_HANDLERS[payload.type];
53
57
  if (handler && typeof this[handler] === "function") {
54
- await this[handler](player, this.current, payload);
58
+ await this[handler](this, this.current, payload);
55
59
  } else {
56
60
  this.handleUnknownEvent(payload);
57
61
  }
58
- };
59
- if (!this.listenerCount("playerUpdate")) {
60
- this.on("playerUpdate", this.onPlayerUpdate);
61
- }
62
- if (!this.listenerCount("event")) {
63
- this.on("event", this.handleEvent);
64
- }
62
+ });
65
63
  }
66
64
 
67
65
  get previous() {
@@ -107,11 +105,13 @@ class Player extends EventEmitter {
107
105
  this.nowPlayingMessage?.delete().catch(() => { });
108
106
  this.aqua.destroyPlayer(this.guildId);
109
107
  this.nodes.rest.destroyPlayer(this.guildId);
108
+ this.clearData();
110
109
  this.removeAllListeners();
111
110
  return this;
112
111
  }
113
112
 
114
113
  pause(paused) {
114
+ if (this.paused === paused) return this;
115
115
  this.paused = paused;
116
116
  this.updatePlayer({ paused });
117
117
  return this;
@@ -184,7 +184,10 @@ class Player extends EventEmitter {
184
184
  return this;
185
185
  }
186
186
  shuffle() {
187
- this.queue = this.queue.sort(() => Math.random() - 0.5);
187
+ for (let i = this.queue.length - 1; i > 0; i--) {
188
+ const j = Math.floor(Math.random() * (i + 1));
189
+ [this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];
190
+ }
188
191
  return this;
189
192
  }
190
193
 
@@ -213,14 +216,16 @@ class Player extends EventEmitter {
213
216
 
214
217
  async trackEnd(player, track, payload) {
215
218
  if (this.shouldDeleteMessage && this.nowPlayingMessage) {
216
- await this.nowPlayingMessage.delete().catch(() => { });
217
- this.nowPlayingMessage = null;
219
+ try {
220
+ await this.nowPlayingMessage.delete();
221
+ this.nowPlayingMessage = null;
222
+ } catch {}
218
223
  }
219
224
 
220
225
  const reason = payload.reason?.replace("_", "").toLowerCase();
221
-
222
- if (reason === "loadfailed" || reason === "cleanup") {
223
- if (player.queue.isEmpty()) {
226
+ if (["loadfailed", "cleanup"].includes(reason)) {
227
+ if (!player.queue.length) {
228
+ this.clearData();
224
229
  this.aqua.emit("queueEnd", player);
225
230
  } else {
226
231
  await player.play();
@@ -228,20 +233,16 @@ class Player extends EventEmitter {
228
233
  return;
229
234
  }
230
235
 
231
- switch (this.loop) {
232
- case Player.LOOP_MODES.TRACK:
233
- this.aqua.emit("trackRepeat", player, track);
234
- player.queue.unshift(track);
235
- break;
236
- case Player.LOOP_MODES.QUEUE:
237
- this.aqua.emit("queueRepeat", player, track);
238
- player.queue.push(track);
239
- break;
236
+ if (this.loop === Player.LOOP_MODES.TRACK) {
237
+ player.queue.unshift(track);
238
+ } else if (this.loop === Player.LOOP_MODES.QUEUE) {
239
+ player.queue.push(track);
240
240
  }
241
241
 
242
242
  if (player.queue.isEmpty()) {
243
243
  this.playing = false;
244
244
  if (this.leaveOnEnd) {
245
+ this.clearData();
245
246
  this.cleanup();
246
247
  }
247
248
  this.aqua.emit("queueEnd", player);
@@ -290,11 +291,11 @@ class Player extends EventEmitter {
290
291
  }
291
292
 
292
293
  clearData() {
294
+ if (this.previousTracks) this.previousTracks.length = 0;
293
295
  this.#dataStore = new WeakMap();
294
296
  return this;
295
297
  }
296
298
 
297
-
298
299
  updatePlayer(data) {
299
300
  return this.nodes.rest.updatePlayer({ guildId: this.guildId, data });
300
301
  }
@@ -313,7 +314,6 @@ class Player extends EventEmitter {
313
314
  updateTrackState(playing, paused) {
314
315
  this.playing = playing;
315
316
  this.paused = paused;
316
- this.updatePlayer({ paused });
317
317
  }
318
318
  }
319
319
 
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
2
  const https = require("https");
3
3
  const http = require("http");
4
+ let http2;
5
+ try {
6
+ http2 = require("http2");
7
+ } catch (e) {
8
+ http2 = null;
9
+ }
4
10
 
5
11
  class Rest {
6
12
  constructor(aqua, { secure, host, port, sessionId, password }) {
@@ -12,7 +18,7 @@ class Rest {
12
18
  "Content-Type": "application/json",
13
19
  Authorization: password,
14
20
  };
15
- this.client = secure ? https : http;
21
+ this.client = secure ? (http2 ? http2 : https) : http;
16
22
  }
17
23
 
18
24
  setSessionId(sessionId) {
@@ -113,4 +119,4 @@ class Rest {
113
119
  }
114
120
  }
115
121
 
116
- module.exports = Rest;
122
+ module.exports = Rest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aqualink",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "An Lavalink client, focused in pure performance and features",
5
5
  "main": "build/index.js",
6
6
  "types": "index.d.ts",
@@ -40,6 +40,9 @@
40
40
  "type": "git",
41
41
  "url": "https://github.com/ToddyTheNoobDud/AquaLink"
42
42
  },
43
+ "dependencies": {
44
+ "ws": "^8.18.1"
45
+ },
43
46
  "maintainers": [
44
47
  {
45
48
  "name": "mushroom0162",
@@ -1,259 +0,0 @@
1
- const https = require('node:https');
2
- const http = require('node:http');
3
- const crypto = require('node:crypto');
4
- const { EventEmitter } = require('node:events');
5
- const { URL } = require('node:url');
6
- const { Buffer } = require('node:buffer');
7
-
8
- let nativeWs = null;
9
- if (process.isBun) nativeWs = require('ws');
10
- const frameHeaderPool = Buffer.alloc(10);
11
-
12
-
13
- class BufferPool {
14
- constructor(initialSize = 8192) {
15
- this.buffer = Buffer.allocUnsafe(initialSize);
16
- this.used = 0;
17
- }
18
-
19
- append(data) {
20
- const dataLength = data.length;
21
- if (this.used + dataLength > this.buffer.length) {
22
- const newSize = Math.max(this.buffer.length * 2, this.used + dataLength);
23
- const newBuffer = Buffer.allocUnsafe(newSize);
24
- this.buffer.copy(newBuffer, 0, 0, this.used);
25
- this.buffer = newBuffer;
26
- }
27
- data.copy(this.buffer, this.used);
28
- this.used += dataLength;
29
- }
30
-
31
- consume(bytes) {
32
- if (bytes === this.used) {
33
- this.used = 0;
34
- return;
35
- }
36
-
37
- this.buffer.copy(this.buffer, 0, bytes, this.used);
38
- this.used -= bytes;
39
- }
40
-
41
- get data() {
42
- return this.buffer.subarray(0, this.used);
43
- }
44
- }
45
-
46
- class WebSocket extends EventEmitter {
47
- constructor(url, options = {}) {
48
- super();
49
- this.url = url;
50
- this.options = options;
51
- this.socket = null;
52
- this.bufferPool = new BufferPool();
53
- this.frameInfo = null;
54
- this.reconnectAttempts = 0;
55
- this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
56
- this.baseReconnectDelay = options.baseReconnectDelay || 1000; // 1 second
57
- this.connect();
58
- }
59
-
60
- connect() {
61
- const { hostname, protocol, port, pathname, search } = new URL(this.url);
62
- const isSecure = protocol === 'wss:';
63
- const agent = isSecure ? https : http;
64
- const key = crypto.randomBytes(16).toString('base64');
65
-
66
- const request = agent.request({
67
- hostname,
68
- port: port || (isSecure ? 443 : 80),
69
- path: pathname + search,
70
- timeout: this.options.timeout || 30000,
71
- headers: {
72
- 'Sec-WebSocket-Key': key,
73
- 'Sec-WebSocket-Version': 13,
74
- 'Upgrade': 'websocket',
75
- 'Connection': 'Upgrade',
76
- ...this.options.headers
77
- },
78
- method: 'GET'
79
- });
80
-
81
- request.once('error', (err) => this._handleError(err));
82
- request.once('upgrade', (res, socket, head) => this._handleUpgrade(res, socket, head, key));
83
- request.end();
84
- }
85
-
86
- _handleUpgrade(res, socket, head, key) {
87
- if (res.headers.upgrade.toLowerCase() !== 'websocket') {
88
- return socket.destroy();
89
- }
90
-
91
- const expectedAccept = crypto.createHash('sha1')
92
- .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
93
- .digest('base64');
94
-
95
- if (res.headers['sec-websocket-accept'] !== expectedAccept) {
96
- return socket.destroy();
97
- }
98
-
99
- this.reconnectAttempts = 0;
100
-
101
- this.socket = socket;
102
- this.socket.setNoDelay(true);
103
- this.socket.setKeepAlive(true);
104
- this.socket.on('data', (data) => this._processData(data));
105
- this.socket.once('close', () => this._handleClose(1006));
106
- this.socket.once('error', (err) => this._handleError(err));
107
- if (head.length) this._processData(head);
108
- this.emit('open');
109
- }
110
-
111
- _processData(data) {
112
- this.bufferPool.append(data);
113
-
114
- while (this.bufferPool.used >= 2) {
115
- const bufferData = this.bufferPool.data;
116
- const lengthByte = bufferData[1] & 127;
117
- let headerSize = 2 + ((bufferData[1] & 128) ? 4 : 0);
118
- if (lengthByte === 126) headerSize += 2;
119
- else if (lengthByte === 127) headerSize += 8;
120
- if (this.bufferPool.used < headerSize) return;
121
-
122
- const frame = this._parseFrame();
123
- if (!frame) return;
124
- this._handleFrame(frame);
125
- }
126
- }
127
-
128
- _parseFrame() {
129
- const bufferData = this.bufferPool.data;
130
- if (bufferData.length < 2) return null;
131
-
132
- const fin = (bufferData[0] & 128) !== 0;
133
- const opcode = bufferData[0] & 15;
134
- let payloadLength = bufferData[1] & 127;
135
- let offset = 2;
136
-
137
- if (payloadLength === 126) {
138
- if (bufferData.length < offset + 2) return null;
139
- payloadLength = bufferData.readUInt16BE(offset);
140
- offset += 2;
141
- } else if (payloadLength === 127) {
142
- if (bufferData.length < offset + 8) return null;
143
- payloadLength = Number(bufferData.readBigUInt64BE(offset));
144
- offset += 8;
145
- }
146
-
147
- const hasMask = (bufferData[1] & 128) !== 0;
148
- const mask = hasMask ? bufferData.subarray(offset, offset + 4) : null;
149
- offset += hasMask ? 4 : 0;
150
-
151
- if (bufferData.length < offset + payloadLength) return null;
152
-
153
- let payload = Buffer.from(bufferData.subarray(offset, offset + payloadLength));
154
-
155
- if (mask) {
156
- for (let i = 0; i < payload.length; i++) {
157
- payload[i] = payload[i] ^ mask[i % 4];
158
- }
159
- }
160
-
161
- const totalFrameSize = offset + payloadLength;
162
- this.bufferPool.consume(totalFrameSize);
163
-
164
- return { fin, opcode, payload };
165
- }
166
-
167
- _handleFrame({ fin, opcode, payload }) {
168
- if (opcode === 0x8) return this._handleClose(payload.length >= 2 ? payload.readUInt16BE(0) : 1006);
169
- if (opcode === 0x9) return this._sendFrame(0xA, payload);
170
- if (opcode === 0xA) return this.emit('pong', payload);
171
- if (!fin) return;
172
-
173
- if (opcode === 0x1) {
174
- this.emit('message', payload.toString('utf-8'));
175
- } else {
176
- this.emit('message', payload);
177
- }
178
- }
179
-
180
- _sendFrame(opcode, payload = Buffer.alloc(0)) {
181
- if (!this.socket || this.socket.destroyed) return;
182
- const length = payload.length;
183
- let headerSize = length < 126 ? 2 : length < 65536 ? 4 : 10;
184
-
185
- frameHeaderPool[0] = 0x80 | opcode;
186
- if (length < 126) {
187
- frameHeaderPool[1] = length;
188
- } else if (length < 65536) {
189
- frameHeaderPool[1] = 126;
190
- frameHeaderPool.writeUInt16BE(length, 2);
191
- } else {
192
- frameHeaderPool[1] = 127;
193
- frameHeaderPool.writeBigUInt64BE(BigInt(length), 2);
194
- }
195
-
196
- this.socket.write(frameHeaderPool.subarray(0, headerSize));
197
- this.socket.write(payload);
198
- }
199
-
200
- send(data) {
201
- if (typeof data === 'string') {
202
- data = Buffer.from(data, 'utf-8');
203
- } else if (!Buffer.isBuffer(data)) {
204
- throw new Error('Data must be a string or Buffer');
205
- }
206
- this._sendFrame(0x1, data);
207
- }
208
-
209
- ping(data = '') {
210
- const pingData = typeof data === 'string' ? Buffer.from(data, 'utf-8') : data;
211
- this._sendFrame(0x9, pingData);
212
- }
213
-
214
- close(code = 1000) {
215
- const codeBuffer = Buffer.allocUnsafe(2);
216
- codeBuffer.writeUInt16BE(code, 0);
217
- this._sendFrame(0x8, codeBuffer);
218
- setTimeout(() => this._handleClose(code), 100);
219
- }
220
-
221
- _handleClose(code) {
222
- if (this.socket) {
223
- this.socket.destroy();
224
- this.socket = null;
225
- }
226
- this.bufferPool = new BufferPool();
227
- this.emit('close', code);
228
- }
229
-
230
- _handleError(err) {
231
- if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') {
232
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
233
- this.reconnectAttempts++;
234
- const delay = Math.min(
235
- this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts - 1),
236
- 30000
237
- );
238
-
239
- const jitter = Math.random() * 0.3 * delay;
240
- const reconnectDelay = delay + jitter;
241
-
242
- setTimeout(() => this.connect(), reconnectDelay);
243
- this.emit('reconnecting', {
244
- attempt: this.reconnectAttempts,
245
- delay: reconnectDelay,
246
- error: err
247
- });
248
- } else {
249
- this.emit('error', new Error(`Maximum reconnection attempts (${this.maxReconnectAttempts}) exceeded: ${err.message}`));
250
- this._handleClose(1006);
251
- }
252
- } else {
253
- this.emit('error', err);
254
- this._handleClose(1006);
255
- }
256
- }
257
- }
258
-
259
- module.exports = nativeWs || WebSocket;