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 +14 -9
- package/build/handlers/fetchImage.js +36 -27
- package/build/index.d.ts +299 -27
- package/build/structures/Node.js +35 -48
- package/build/structures/Player.js +31 -31
- package/build/structures/Rest.js +8 -2
- package/package.json +4 -1
- package/build/handlers/WebSocket.js +0 -259
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
|
-
-
|
|
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.
|
|
26
|
+
# Tralalero Tralala 2.0.2 Released
|
|
26
27
|
---
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- Rewrite
|
|
32
|
-
-
|
|
33
|
-
-
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 "
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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:
|
|
17
|
-
createPlayer(node: Node, options:
|
|
7
|
+
createNode(options: NodeConfig): Node;
|
|
8
|
+
createPlayer(node: Node, options: PlayerOptions): Player;
|
|
18
9
|
destroyPlayer(guildId: string): Promise<void>;
|
|
19
|
-
resolve(options:
|
|
20
|
-
updateVoiceState(data:
|
|
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?:
|
|
116
|
+
constructor(player: Player, options?: FiltersOptions);
|
|
31
117
|
setEqualizer(bands: Array<any>): Promise<void>;
|
|
32
|
-
setKaraoke(enabled: boolean, options?:
|
|
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:
|
|
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?:
|
|
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():
|
|
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:
|
|
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:
|
|
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
|
}
|
package/build/structures/Node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const WebSocket = require('
|
|
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.
|
|
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
|
|
101
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 ||
|
|
165
|
-
freePercentage: allocated ? (free / allocated) * 100 :
|
|
166
|
-
usedPercentage: allocated ? (used / allocated) * 100 :
|
|
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
|
-
|
|
171
|
-
const cores = cpu.cores ||
|
|
172
|
-
|
|
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 ||
|
|
175
|
-
lavalinkLoad: cpu.lavalinkLoad ||
|
|
176
|
-
lavalinkLoadPercentage: cores ? (cpu.lavalinkLoad / cores) * 100 :
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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](
|
|
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
|
-
|
|
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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
223
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
package/build/structures/Rest.js
CHANGED
|
@@ -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.
|
|
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;
|