aqualink 1.7.0-beta5 → 1.8.0

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
@@ -11,14 +11,14 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
11
11
  - Lowest CPU Usage
12
12
  - Very fast (mine take less than 1 second to load an song!)
13
13
  - 1 Player created = ~1 - 0,5 mb per player
14
- - Auto clean Up memory when song finishes / bot leave the vc
14
+ - Auto clean Up memory when song finishes / bot leave the vc (Now Options supported!)
15
15
  - Plugin system
16
16
  - Lavalink v4 support (din't test v3)
17
- - Youtube and Spotify support
17
+ - Youtube and Spotify support (Soundcloud, deezer, vimeo, etc also works...)
18
18
  - Minimal Requests to the lavalink server (helps the lavalink recourses!)
19
- - Easy player, node, aqua managing
19
+ - Easy player, node, aqua manager
20
20
  - Fast responses from rest and node
21
- - Playlist support (My mix playlists, youtube playlists, spotify playlists)
21
+ - Playlist support (My mix playlists, youtube playlists, spotify playlists, etc)
22
22
  - Lyrics Support by Lavalink
23
23
  - https://github.com/topi314/LavaLyrics (RECOMMENDED)
24
24
  - https://github.com/DRSchlaubi/lyrics.kt (?)
@@ -29,50 +29,45 @@ This code is based in riffy, but its an 100% Rewrite made from scratch...
29
29
 
30
30
  - Example bot: https://github.com/ToddyTheNoobDud/Thorium-Music
31
31
 
32
- # Real changelog for 1.7.0-beta1
33
- Note: Not features are widely tested / Not fully Complete
34
-
35
- - Reformated the `PLAYER` System (removed Documentation for now)
36
- - Notable Changes:
37
- - New WeakMap System (Properly handling, deleting, setting)
38
- - Around 2x faster (by my tests, taked 0ms to resolve an song)
39
- - Uses less recourses (reduced by around ~0,5mb, also less cpu instensive)
40
-
41
- - Fix Some errors in `REST`, Now destroyPlayer, etc, should work as expected.
42
- - Rewrited out the `TRACK` System
43
- - Reduced object creation
44
- - Use direct acess
45
- - use direct destroy() instead of Object.assing()
46
- - Separate _findMatchingTrack()
47
- - Rewrite the search system, Removed useless caching, Improved speed, use traditional for ... of instead of find() - Experimental
48
-
49
- - Rewrited out the `NODE` System
50
- - Implement the InfiniteReconnects Option (this will make the code try to connect to an node non-stop.)
51
- - WeakMap has been replaced with statsCache (experimental)
52
- - Optimized by using free, used and allocated direct.
53
- - Backoff in reconnect logic (by using Math)
54
- - Clear reconnectTimeoutId (prevent memory leaks)
55
- - Improve the overall speed by a bit
56
-
57
- - Rewrited the `CONNECTION` System
58
- - Improved the Connecting, Resolving, Reconnecting Speed (around 1,5x faster now)
59
- - Improved checking
60
- - Cached frequently used Code
61
- - Object.assign Implemented for Batch updates
62
- - Still in testing, pls report bugs
63
-
64
- - Some Additions for `AQUA`
65
- - Implement the InfiniteReconnects Options
66
- - Re-added our DOCS (Now autocomplete works again!)
67
- - Add platforms + search system on DefaultPlatform
68
-
69
- - Rewrited the updateVoiceState System
70
- - Misc changes to createConnection
71
- - Document + fix destroyPlayer
72
-
73
- - Remade some stuff in `FetchImage`
74
- - Use promise.race since only first sucess is required. (will be tested, may revert to promise.any)
75
- - Use map cuz its faster and more efficient than Objects.
32
+ # Brick by brick, 1.8.0 Update (yay)
33
+
34
+ - Misc changes on FetchImage (improves the overall checking and speed)
35
+
36
+ - Rewrite `AQUA` module
37
+ - Remade the resolve logic (improves the speed by a lot)
38
+ - Fixes many memory usages related to nodes
39
+ - send is no longer required to be Applied (Applied by default now.)
40
+ - Remade some stuff with discord VoiceGateway
41
+
42
+ - Remade `CONNECTION` module
43
+ - Way faster connections (Joining, reconnecting, connected, disconnect)
44
+ - Reduced memory overload by removing useless code
45
+ - Improved early Returns
46
+
47
+ - Remade `NODE` module
48
+ - MANY fixes for the connection logic (fixes reconnection, etc)
49
+ - Fixed memory leaks in heartbeat system (hopefully, reduced memory by a lot.)
50
+ - Faster connection speed and checkings
51
+ - Remade the Options system, improve JSON parsing
52
+
53
+ - Rewrite `PLAYER` module
54
+ - Many memory related fixes
55
+ - Improved the overall code speed by a lot
56
+ - Rewrote setLoop, play, shuffle, replay methods (fixes + performance)
57
+ - Added 2 new options:
58
+
59
+ leaveOnEnd: false, // Optional
60
+
61
+ shouldDeleteMessage: true // Optional
62
+
63
+ - Uses array for better performance and less memory allocation
64
+ - Rewrite the Events handling (speed and recourses fixes)
65
+
66
+ - Updated `TRACK` module
67
+ - Better object handling for internal code.
68
+ - Removed an useless method
69
+
70
+ Thats all for now, im lazy, help me fix code and improve this on github... i can't test properly 😭😭😭
76
71
 
77
72
  # How to install
78
73
 
@@ -115,15 +110,12 @@ const nodes = [
115
110
  ];
116
111
 
117
112
  const aqua = new Aqua(client, nodes, {
118
- send: (payload) => {
119
- const guild = client.guilds.cache.get(payload.d.guild_id);
120
- if (guild) guild.shard.send(payload);
121
- },
122
- defaultSearchPlatform: "ytsearch",
123
- restVersion: "v4"
113
+ defaultSearchPlatform: "ytsearch",
114
+ restVersion: "v4",
115
+ autoResume: false,
116
+ infiniteReconnects: true,
124
117
  });
125
118
 
126
-
127
119
  client.aqua = aqua;
128
120
 
129
121
 
@@ -1,19 +1,15 @@
1
1
  const { request } = require("undici");
2
-
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
8
 
11
9
  async function getImageUrl(info) {
12
10
  if (!info?.sourceName || !info?.uri) return null;
13
-
14
11
  const handler = sourceHandlers.get(info.sourceName.toLowerCase());
15
12
  if (!handler) return null;
16
-
17
13
  try {
18
14
  return await handler(info.uri);
19
15
  } catch (error) {
@@ -31,18 +27,19 @@ async function fetchThumbnail(url) {
31
27
  const json = await body.json();
32
28
  return json.thumbnail_url || null;
33
29
  } catch (error) {
34
- console.error('Error fetching thumbnail:', error);
30
+ console.error(`Error fetching thumbnail from ${url}:`, error);
35
31
  return null;
36
32
  }
37
33
  }
38
34
 
39
35
  async function fetchYouTubeThumbnail(identifier) {
40
- const fetchPromises = YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)));
41
-
42
36
  try {
43
- return await Promise.race(fetchPromises);
37
+ const thumbnail = await Promise.race(
38
+ YOUTUBE_QUALITIES.map(urlFunc => fetchThumbnail(urlFunc(identifier)))
39
+ );
40
+ return thumbnail || null;
44
41
  } catch (error) {
45
- console.error('Error fetching YouTube thumbnail:', error);
42
+ console.error('No valid YouTube thumbnail found:', error);
46
43
  return null;
47
44
  }
48
45
  }
@@ -6,28 +6,18 @@ const { version: pkgVersion } = require("../../package.json");
6
6
  const URL_REGEX = /^https?:\/\//;
7
7
 
8
8
  class Aqua extends EventEmitter {
9
- /**
9
+ /**
10
10
  * @param {Object} client - The client instance.
11
11
  * @param {Array<Object>} nodes - An array of node configurations.
12
12
  * @param {Object} options - Configuration options for Aqua.
13
13
  * @param {Function} options.send - Function to send data.
14
- * @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform. Options include:
15
- * - "youtube music": "ytmsearch"
16
- * - "youtube": "ytsearch"
17
- * - "spotify": "spsearch"
18
- * - "jiosaavn": "jssearch"
19
- * - "soundcloud": "scsearch"
20
- * - "deezer": "dzsearch"
21
- * - "tidal": "tdsearch"
22
- * - "applemusic": "amsearch"
23
- * - "bandcamp": "bcsearch"
14
+ * @param {string} [options.defaultSearchPlatform="ytsearch"] - Default search platform.
24
15
  * @param {string} [options.restVersion="v4"] - Version of the REST API.
25
16
  * @param {Array<Object>} [options.plugins=[]] - Plugins to load.
26
- * @param {string} [options.shouldDeleteMessage='none'] - Should delete your message? (true, false)
27
17
  * @param {boolean} [options.autoResume=false] - Automatically resume tracks on reconnect.
28
- * @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely (default: false).
18
+ * @param {boolean} [options.infiniteReconnects=false] - Reconnect infinitely.
29
19
  */
30
- constructor(client, nodes, options) {
20
+ constructor(client, nodes, options = {}) {
31
21
  super();
32
22
  this.validateInputs(client, nodes, options);
33
23
  this.client = client;
@@ -36,26 +26,46 @@ class Aqua extends EventEmitter {
36
26
  this.players = new Map();
37
27
  this.clientId = null;
38
28
  this.initiated = false;
39
- this.shouldDeleteMessage = options.shouldDeleteMessage || false;
40
- this.defaultSearchPlatform = options.defaultSearchPlatform || "ytsearch";
41
- this.restVersion = options.restVersion || "v4";
42
- this.plugins = options.plugins || [];
43
- this.version = pkgVersion;
44
29
  this.options = options;
45
- this.send = options.send;
46
- this.autoResume = options.autoResume || false;
47
- this.infiniteReconnects = options.infiniteReconnects || false;
30
+ this.shouldDeleteMessage = this.getOption(options, 'shouldDeleteMessage', false);
31
+ this.defaultSearchPlatform = this.getOption(options, 'defaultSearchPlatform', 'ytsearch');
32
+ this.leaveOnEnd = this.getOption(options, 'leaveOnEnd', true);
33
+ this.restVersion = this.getOption(options, 'restVersion', 'v4');
34
+ this.plugins = this.getOption(options, 'plugins', []);
35
+ this.version = pkgVersion;
36
+ this.send = options.send || this.defaultSendFunction;
37
+ this.autoResume = this.getOption(options, 'autoResume', false);
38
+ this.infiniteReconnects = this.getOption(options, 'infiniteReconnects', false);
48
39
  this.setMaxListeners(0);
40
+ this._leastUsedCache = { nodes: [], timestamp: 0 };
41
+ }
42
+
43
+ getOption(options, key, defaultValue) {
44
+ return Object.prototype.hasOwnProperty.call(options, key) ? options[key] : defaultValue;
45
+ }
46
+
47
+ defaultSendFunction(payload) {
48
+ const guild = this.client.guilds.cache.get(payload.d.guild_id);
49
+ if (guild) guild.shard.send(payload);
49
50
  }
50
51
 
51
- validateInputs(client, nodes, options) {
52
+ validateInputs(client, nodes) {
52
53
  if (!client) throw new Error("Client is required to initialize Aqua");
53
- if (!Array.isArray(nodes) || !nodes.length) throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
54
- if (typeof options?.send !== "function") throw new Error("Send function is required to initialize Aqua");
54
+ if (!Array.isArray(nodes) || !nodes.length) {
55
+ throw new Error(`Nodes must be a non-empty Array (Received ${typeof nodes})`);
56
+ }
55
57
  }
56
58
 
57
59
  get leastUsedNodes() {
58
- return [...this.nodeMap.values()].filter(node => node.connected).sort((a, b) => a.rest.calls - b.rest.calls);
60
+ const now = Date.now();
61
+ if (now - this._leastUsedCache.timestamp < 50) return this._leastUsedCache.nodes;
62
+ const nodes = [];
63
+ for (const node of this.nodeMap.values()) {
64
+ if (node.connected) nodes.push(node);
65
+ }
66
+ nodes.sort((a, b) => a.rest.calls - b.rest.calls);
67
+ this._leastUsedCache = { nodes, timestamp: now };
68
+ return nodes;
59
69
  }
60
70
 
61
71
  init(clientId) {
@@ -74,45 +84,55 @@ class Aqua extends EventEmitter {
74
84
 
75
85
  createNode(options) {
76
86
  const nodeId = options.name || options.host;
77
- this.destroyNode(nodeId); // Ensure no duplicate nodes
87
+ this.destroyNode(nodeId);
78
88
  const node = new Node(this, options, this.options);
79
89
  this.nodeMap.set(nodeId, node);
80
- node.connect()
81
- .then(() => this.emit("nodeCreate", node))
82
- .catch(error => {
83
- this.nodeMap.delete(nodeId);
84
- throw error;
85
- });
90
+ this._leastUsedCache.timestamp = 0;
91
+ node.connect().then(() => {
92
+ this.emit("nodeCreate", node);
93
+ }).catch(error => {
94
+ this.nodeMap.delete(nodeId);
95
+ throw error;
96
+ });
86
97
  return node;
87
98
  }
88
99
 
89
100
  destroyNode(identifier) {
90
101
  const node = this.nodeMap.get(identifier);
91
102
  if (!node) return;
92
-
93
- node.disconnect()
94
- .then(() => {
95
- node.removeAllListeners();
96
- this.nodeMap.delete(identifier);
97
- this.emit("nodeDestroy", node);
98
- })
99
- .catch(error => console.error(`Error destroying node ${identifier}:`, error));
103
+ node.disconnect().then(() => {
104
+ node.removeAllListeners();
105
+ this.nodeMap.delete(identifier);
106
+ this._leastUsedCache.timestamp = 0;
107
+ this.emit("nodeDestroy", node);
108
+ }).catch(error => console.error(`Error destroying node ${identifier}:`, error));
100
109
  }
101
110
 
102
111
  updateVoiceState({ d, t }) {
103
112
  const player = this.players.get(d.guild_id);
104
- if (player && (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId))) {
105
- player.connection[t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate"](d);
106
- if (d.status === "disconnected") this.cleanupPlayer(player);
113
+ if (!player) return;
114
+ if (t === "VOICE_SERVER_UPDATE" || (t === "VOICE_STATE_UPDATE" && d.user_id === this.clientId)) {
115
+ const updateMethod = t === "VOICE_SERVER_UPDATE" ? "setServerUpdate" : "setStateUpdate";
116
+ if (player.connection && typeof player.connection[updateMethod] === "function") {
117
+ player.connection[updateMethod](d);
118
+ }
119
+ if (d.status === "disconnected") {
120
+ this.cleanupPlayer(player);
121
+ }
107
122
  }
108
123
  }
109
124
 
110
125
  fetchRegion(region) {
111
126
  if (!region) return this.leastUsedNodes;
112
127
  const lowerRegion = region.toLowerCase();
113
- return [...this.nodeMap.values()]
114
- .filter(node => node.connected && node.regions?.includes(lowerRegion))
115
- .sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
128
+ const regionNodes = [];
129
+ for (const node of this.nodeMap.values()) {
130
+ if (node.connected && node.regions?.includes(lowerRegion)) {
131
+ regionNodes.push(node);
132
+ }
133
+ }
134
+ regionNodes.sort((a, b) => this.calculateLoad(a) - this.calculateLoad(b));
135
+ return regionNodes;
116
136
  }
117
137
 
118
138
  calculateLoad(node) {
@@ -123,10 +143,10 @@ class Aqua extends EventEmitter {
123
143
 
124
144
  createConnection(options) {
125
145
  this.ensureInitialized();
126
- const player = this.players.get(options.guildId);
127
- if (player && player.voiceChannel) return player;
128
-
129
- const node = options.region ? this.fetchRegion(options.region)[0] : this.leastUsedNodes[0];
146
+ const existingPlayer = this.players.get(options.guildId);
147
+ if (existingPlayer && existingPlayer.voiceChannel) return existingPlayer;
148
+ const availableNodes = options.region ? this.fetchRegion(options.region) : this.leastUsedNodes;
149
+ const node = availableNodes[0];
130
150
  if (!node) throw new Error("No nodes are available");
131
151
  return this.createPlayer(node, options);
132
152
  }
@@ -144,9 +164,8 @@ class Aqua extends EventEmitter {
144
164
  async destroyPlayer(guildId) {
145
165
  const player = this.players.get(guildId);
146
166
  if (!player) return;
147
-
148
167
  try {
149
- await player.clearData(); // Assuming clearData is an async function
168
+ await player.clearData();
150
169
  player.removeAllListeners();
151
170
  this.players.delete(guildId);
152
171
  this.emit("playerDestroy", player);
@@ -159,6 +178,7 @@ class Aqua extends EventEmitter {
159
178
  this.ensureInitialized();
160
179
  const requestNode = this.getRequestNode(nodes);
161
180
  const formattedQuery = this.formatQuery(query, source);
181
+
162
182
  try {
163
183
  const response = await requestNode.rest.makeRequest("GET", `/v4/loadtracks?identifier=${encodeURIComponent(formattedQuery)}`);
164
184
  if (["empty", "NO_MATCHES"].includes(response.loadType)) {
@@ -166,7 +186,7 @@ class Aqua extends EventEmitter {
166
186
  }
167
187
  return this.constructorResponse(response, requester, requestNode);
168
188
  } catch (error) {
169
- if (error.name === 'AbortError') {
189
+ if (error.name === "AbortError") {
170
190
  throw new Error("Request timed out");
171
191
  }
172
192
  throw new Error(`Failed to resolve track: ${error.message}`);
@@ -177,7 +197,7 @@ class Aqua extends EventEmitter {
177
197
  if (nodes && !(typeof nodes === "string" || nodes instanceof Node)) {
178
198
  throw new TypeError(`'nodes' must be a string or Node instance, received: ${typeof nodes}`);
179
199
  }
180
- return (typeof nodes === 'string' ? this.nodeMap.get(nodes) : nodes) ?? this.leastUsedNodes[0];
200
+ return (typeof nodes === "string" ? this.nodeMap.get(nodes) : nodes) ?? this.leastUsedNodes[0];
181
201
  }
182
202
 
183
203
  ensureInitialized() {
@@ -190,9 +210,11 @@ class Aqua extends EventEmitter {
190
210
 
191
211
  async handleNoMatches(rest, query) {
192
212
  try {
193
- const youtubeResponse = await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`);
213
+ const ytIdentifier = `/v4/loadtracks?identifier=https://www.youtube.com/watch?v=${query}`;
214
+ const youtubeResponse = await rest.makeRequest("GET", ytIdentifier);
194
215
  if (["empty", "NO_MATCHES"].includes(youtubeResponse.loadType)) {
195
- return await rest.makeRequest("GET", `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`);
216
+ const spotifyIdentifier = `/v4/loadtracks?identifier=https://open.spotify.com/track/${query}`;
217
+ return await rest.makeRequest("GET", spotifyIdentifier);
196
218
  }
197
219
  return youtubeResponse;
198
220
  } catch (error) {
@@ -206,7 +228,7 @@ class Aqua extends EventEmitter {
206
228
  exception: null,
207
229
  playlistInfo: null,
208
230
  pluginInfo: response.pluginInfo ?? {},
209
- tracks: [],
231
+ tracks: []
210
232
  };
211
233
 
212
234
  if (response.loadType === "error" || response.loadType === "LOAD_FAILED") {
@@ -214,7 +236,8 @@ class Aqua extends EventEmitter {
214
236
  return baseResponse;
215
237
  }
216
238
 
217
- const trackFactory = (trackData) => new Track(trackData, requester, requestNode);
239
+ const trackFactory = trackData => new Track(trackData, requester, requestNode);
240
+
218
241
  switch (response.loadType) {
219
242
  case "track":
220
243
  if (response.data) {
@@ -225,7 +248,7 @@ class Aqua extends EventEmitter {
225
248
  if (response.data?.info) {
226
249
  baseResponse.playlistInfo = {
227
250
  name: response.data.info.name ?? response.data.info.title,
228
- ...response.data.info,
251
+ ...response.data.info
229
252
  };
230
253
  }
231
254
  baseResponse.tracks = (response.data?.tracks ?? []).map(trackFactory);
@@ -243,45 +266,10 @@ class Aqua extends EventEmitter {
243
266
  return player;
244
267
  }
245
268
 
246
- cleanupIdle() {
247
- const now = Date.now();
248
- for (const [guildId, player] of this.players) {
249
- if (!player.playing && !player.paused && player.queue.isEmpty() && (now - player.lastActivity) > this.options.idleTimeout) {
250
- this.cleanupPlayer(player);
251
- }
252
- }
253
- }
254
-
255
269
  cleanupPlayer(player) {
256
- if (!player) return;
257
- try {
258
- player.clearData();
259
- player.removeAllListeners();
260
- player.destroy();
270
+ if (player && this.players.has(player.guildId)) {
261
271
  this.players.delete(player.guildId);
262
- this.emit("playerDestroy", player);
263
- } catch (error) {
264
- console.error(`Error during player cleanup: ${error.message}`);
265
- }
266
- }
267
-
268
- cleanup() {
269
- for (const player of this.players.values()) {
270
- this.cleanupPlayer(player);
271
- }
272
- for (const node of this.nodeMap.values()) {
273
- this.destroyNode(node.name || node.host);
274
272
  }
275
- this.nodeMap.clear();
276
- this.players.clear();
277
- this.client = null;
278
- this.nodes = null;
279
- this.plugins?.forEach(plugin => plugin.unload?.(this));
280
- this.plugins = null;
281
- this.options = null;
282
- this.send = null;
283
- this.version = null;
284
- this.removeAllListeners();
285
273
  }
286
274
  }
287
275
 
@@ -1,100 +1,74 @@
1
1
  class Connection {
2
2
  constructor(player) {
3
- this.player = player;
4
- this.voice = {
5
- sessionId: null,
6
- endpoint: null,
7
- token: null
8
- };
9
- this.region = null;
10
- this.selfDeaf = false;
11
- this.selfMute = false;
12
- this.voiceChannel = player.voiceChannel;
13
- this.guildId = player.guildId;
14
- this.aqua = player.aqua;
15
- this.nodes = player.nodes;
3
+ this.playerRef = new WeakRef(player);
4
+ const { voiceChannel, guildId, aqua, nodes } = player;
5
+ this.voice = {
6
+ sessionId: null,
7
+ endpoint: null,
8
+ token: null
9
+ };
10
+ this.region = null;
11
+ this.selfDeaf = false;
12
+ this.selfMute = false;
13
+ this.voiceChannel = voiceChannel;
14
+ this.guildId = guildId;
15
+ this.aqua = aqua;
16
+ this.nodes = nodes;
16
17
  }
17
18
 
18
19
  setServerUpdate(data) {
19
- if (!data?.endpoint) {
20
- throw new Error("Missing 'endpoint' property");
21
- }
20
+ const { endpoint, token } = data || {};
21
+ if (!endpoint) {
22
+ throw new Error("Missing 'endpoint' property");
23
+ }
24
+ const regionMatch = endpoint.match(/^([a-zA-Z]+)/);
25
+ if (!regionMatch) return;
26
+ const newRegion = regionMatch[1];
22
27
 
23
- const endpoint = data.endpoint;
24
- const regionMatch = endpoint.match(/^([a-zA-Z]+)/);
25
- if (!regionMatch) return;
26
-
27
- const newRegion = regionMatch[1];
28
- if (this.region !== newRegion) {
29
- this.voice.endpoint = endpoint;
30
- this.voice.token = data.token;
31
-
32
- const prevRegion = this.region;
33
- this.region = newRegion;
34
-
35
- this.aqua.emit(
36
- "debug",
37
- `[Player ${this.guildId} - CONNECTION] ${
38
- prevRegion
39
- ? `Changed Voice Region from ${prevRegion} to ${newRegion}`
40
- : `Voice Server: ${newRegion}`
41
- }`
42
- );
43
- }
44
-
45
- this._updatePlayerVoiceData();
28
+ if (this.region !== newRegion) {
29
+ this.voice.endpoint = endpoint;
30
+ this.voice.token = token;
31
+ const prevRegion = this.region;
32
+ this.region = newRegion;
33
+ this.aqua.emit(
34
+ "debug",
35
+ `[Player ${this.guildId} - CONNECTION] ${
36
+ prevRegion
37
+ ? `Changed Voice Region from ${prevRegion} to ${newRegion}`
38
+ : `Voice Server: ${newRegion}`
39
+ }`
40
+ );
41
+ }
42
+ this._updatePlayerVoiceData();
46
43
  }
47
44
 
48
45
  setStateUpdate(data) {
49
- const { channel_id: channelId, session_id: sessionId, self_deaf: selfDeaf, self_mute: selfMute } = data;
50
-
51
- if (!channelId || !sessionId) {
52
- this._cleanup();
53
- return;
54
- }
46
+ const { channel_id: channelId, session_id: sessionId, self_deaf: selfDeaf, self_mute: selfMute } = data || {};
47
+ if (!channelId || !sessionId) return;
55
48
 
56
- if (this.voiceChannel !== channelId) {
57
- this.aqua.emit("playerMove", this.voiceChannel, channelId);
58
- this.voiceChannel = channelId;
59
- }
60
-
61
- this.selfDeaf = selfDeaf;
62
- this.selfMute = selfMute;
63
- this.voice.sessionId = sessionId;
49
+ if (this.voiceChannel !== channelId) {
50
+ this.aqua.emit("playerMove", this.voiceChannel, channelId);
51
+ this.voiceChannel = channelId;
52
+ }
53
+ this.selfDeaf = selfDeaf;
54
+ this.selfMute = selfMute;
55
+ this.voice.sessionId = sessionId;
64
56
  }
65
57
 
66
58
  async _updatePlayerVoiceData() {
67
- try {
68
- await this.nodes.rest.updatePlayer({
69
- guildId: this.guildId,
70
- data: {
71
- voice: this.voice,
72
- volume: this.player.volume
73
- }
74
- });
75
- } catch (err) {
76
- this.aqua.emit("apiError", "updatePlayer", err);
77
- }
78
- }
79
-
80
- _cleanup() {
81
- const { aqua, player, voiceChannel } = this;
82
-
83
- aqua.emit("playerLeave", voiceChannel);
84
-
85
- player.voiceChannel = null;
86
- this.voiceChannel = null;
87
- player.destroy();
88
-
89
- aqua.emit("playerDestroy", player);
90
- this.player = null;
91
- this.voice = null;
92
- this.region = null;
93
- this.selfDeaf = null;
94
- this.selfMute = null;
95
- this.guildId = null;
96
- this.aqua = null;
97
- this.nodes = null;
59
+ const player = this.playerRef.deref();
60
+ if (!player) return;
61
+ try {
62
+ await this.nodes.rest.updatePlayer({
63
+ guildId: this.guildId,
64
+ data: {
65
+ voice: this.voice,
66
+ volume: player.volume
67
+ }
68
+ });
69
+ } catch (err) {
70
+ this.aqua.emit("apiError", "updatePlayer", err);
71
+ }
98
72
  }
99
73
  }
100
74