aqualink 2.9.13 → 2.10.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.
@@ -1,130 +1,133 @@
1
1
  'use strict'
2
2
 
3
- const LEGACY_ENDPOINT_REGEX = /^([a-z\-]+)\d+/i
4
- const MODERN_ENDPOINT_REGEX = /^([a-z\-]+)-\d+\.discord\.media(?::\d+)?$/i
3
+ const ENDPOINT_REGEX = /^([a-z-]+)(?:-\d+\.discord\.media(?::\d+)?|\d+)/i
5
4
 
6
5
  class Connection {
7
6
  constructor(player) {
8
- this.player = player
9
- this.voiceChannel = player.voiceChannel
10
- this.guildId = player.guildId
11
- this.aqua = player.aqua
12
- this.nodes = player.nodes
7
+ this._player = player
8
+ this._aqua = player.aqua
9
+ this._nodes = player.nodes
10
+ this._guildId = player.guildId
13
11
 
12
+ this.voiceChannel = player.voiceChannel
14
13
  this.sessionId = null
15
14
  this.endpoint = null
16
15
  this.token = null
17
16
  this.region = null
17
+
18
+ this.sequence = 0
19
+ this._lastEndpoint = null
18
20
  }
19
21
 
20
- _extractRegionFromEndpoint(endpoint) {
21
- if (!endpoint) return null
22
+ _extractRegion(endpoint) {
23
+ const match = endpoint?.match(ENDPOINT_REGEX)
24
+ return match?.[1] || null
25
+ }
22
26
 
23
- const modernMatch = endpoint.match(MODERN_ENDPOINT_REGEX)
24
- if (modernMatch) return modernMatch[1]
27
+ setServerUpdate(data) {
28
+ if (!data?.endpoint || !data?.token) return
25
29
 
26
- const legacyMatch = endpoint.match(LEGACY_ENDPOINT_REGEX)
27
- if (legacyMatch) return legacyMatch[1]
30
+ const newEndpoint = data.endpoint.trim()
31
+ const newRegion = this._extractRegion(newEndpoint)
32
+ const regionChanged = this.region !== newRegion
33
+ const endpointChanged = this._lastEndpoint !== newEndpoint
28
34
 
29
- this.aqua.emit('debug', `[Player ${this.guildId}] Failed to parse endpoint: ${endpoint}`)
30
- return 'unknown'
31
- }
35
+ if (regionChanged || endpointChanged) {
36
+ if (regionChanged && this._aqua.listenerCount('debug') > 0) {
37
+ this._aqua.emit('debug', `[Player ${this._guildId}] Region: ${this.region || 'none'} → ${newRegion}`)
38
+ }
32
39
 
33
- setServerUpdate(data) {
34
- if (!data?.endpoint || !data?.token) {
35
- this.aqua.emit('debug', `[Player ${this.guildId}] Incomplete server update`)
36
- return
37
- }
40
+ if (endpointChanged) {
41
+ this.sequence = 0
42
+ this._lastEndpoint = newEndpoint
43
+ }
38
44
 
39
- const fullEndpoint = data.endpoint.trim()
40
- const newRegion = this._extractRegionFromEndpoint(fullEndpoint)
45
+ this.endpoint = newEndpoint
46
+ this.region = newRegion
47
+ }
41
48
 
42
- const oldRegion = this.region
43
- this.endpoint = fullEndpoint
44
49
  this.token = data.token
45
- this.region = newRegion
46
50
 
47
- this.aqua.emit('debug', `[Player ${this.guildId}] Voice server updated: ${oldRegion ? `${oldRegion} → ${newRegion}` : newRegion}, endpoint: ${fullEndpoint}`)
51
+ if (this._player.paused) {
52
+ this._player.paused = false
53
+ }
48
54
 
49
- if (this.player.paused) this.player.paused = false
50
- this._updatePlayerVoiceData()
55
+ this._updateVoiceData()
51
56
  }
52
57
 
53
58
  setStateUpdate(data) {
54
- if (!data || data.user_id !== this.aqua.clientId) return
55
-
56
- const sessionId = data.session_id
57
- const channelId = data.channel_id
58
- const selfDeaf = data.self_deaf
59
- const selfMute = data.self_mute
60
-
61
- if (channelId) {
62
- if (this.voiceChannel !== channelId) {
63
- this.aqua.emit('playerMove', this.voiceChannel, channelId)
64
- this.voiceChannel = channelId
65
- this.player.voiceChannel = channelId
59
+ if (!data || data.user_id !== this._aqua.clientId) return
60
+
61
+ const { session_id, channel_id, self_deaf, self_mute } = data
62
+
63
+ if (channel_id) {
64
+ if (this.voiceChannel !== channel_id) {
65
+ if (this._aqua.listenerCount('playerMove') > 0) {
66
+ this._aqua.emit('playerMove', this.voiceChannel, channel_id)
67
+ }
68
+ this.voiceChannel = channel_id
69
+ this._player.voiceChannel = channel_id
70
+ }
71
+
72
+ if (this.sessionId !== session_id) {
73
+ this.sessionId = session_id
66
74
  }
67
75
 
68
- this.player.self_deaf = !!selfDeaf
69
- this.player.self_mute = !!selfMute
70
- this.sessionId = sessionId
71
- this.voiceChannel = channelId
76
+ this._player.self_deaf = !!self_deaf
77
+ this._player.self_mute = !!self_mute
78
+ this._player.connected = true
72
79
 
73
- this.aqua.emit('debug', `[Player ${this.guildId}] Voice state updated - session: ${sessionId}, channel: ${channelId}`)
74
- this.player.connected = true
80
+ this._updateVoiceData()
75
81
  } else {
76
- this.aqua.emit('debug', `[Player ${this.guildId}] Voice state updated - disconnected`)
77
- if (this.player) {
78
- this._destroyPlayer()
79
- this.voiceChannel = null;
80
- this.player.voiceChannel = null;
81
- this.aqua.emit('playerDestroy', this.player)
82
- } else {
83
- this.aqua.destroyPlayer(this.guildId)
84
- this.voiceChannel = null;
85
- this.player.voiceChannel = null;
86
- this.aqua.emit('playerDestroy', this.player)
87
- }
82
+ this._handleDisconnect()
88
83
  }
89
84
  }
90
85
 
91
- _destroyPlayer() {
92
- this.aqua.emit('debug', `[Player ${this.guildId}] Destroying player due to voice disconnect`)
93
- this.player.destroy()
94
- this.aqua.emit('playerDestroy', this.player)
86
+ _handleDisconnect() {
87
+ if (!this._player.connected) return
88
+
89
+ this._aqua.emit('debug', `[Player ${this._guildId}] Disconnected`)
90
+
91
+ this.voiceChannel = null
92
+ this.sessionId = null
93
+ this.sequence = 0
94
+
95
+ this._player.destroy()
95
96
  }
96
97
 
97
- _updatePlayerVoiceData() {
98
- if (!this.sessionId || !this.endpoint || !this.token) {
99
- this.aqua.emit('debug', `[Player ${this.guildId}] Incomplete voice data, waiting... (session: ${!!this.sessionId}, endpoint: ${!!this.endpoint}, token: ${!!this.token})`)
100
- return
101
- }
98
+ updateSequence(seq) {
99
+ this.sequence = seq > this.sequence ? seq : this.sequence
100
+ }
102
101
 
103
- this.aqua.emit('debug', `[Player ${this.guildId}] Updating player voice data with endpoint: ${this.endpoint}`)
104
-
105
- try {
106
- this.nodes.rest.updatePlayer({
107
- guildId: this.guildId,
108
- data: {
109
- voice: {
110
- token: this.token,
111
- endpoint: this.endpoint,
112
- sessionId: this.sessionId
113
- },
114
- volume: this.player.volume
115
- }
116
- })
117
- } catch (error) {
118
- this.aqua.emit('debug', 'updatePlayer', {
119
- error: error.message,
120
- guildId: this.guildId,
102
+ _updateVoiceData(isResume = false) {
103
+ if (!this.sessionId || !this.endpoint || !this.token) return
104
+
105
+ const payload = {
106
+ guildId: this._guildId,
107
+ data: {
121
108
  voice: {
122
109
  token: this.token,
123
110
  endpoint: this.endpoint,
124
111
  sessionId: this.sessionId
125
- }
126
- })
112
+ },
113
+ volume: this._player.volume
114
+ }
115
+ }
116
+
117
+ if (isResume) {
118
+ payload.data.voice.resume = true
119
+ payload.data.voice.sequence = this.sequence
127
120
  }
121
+
122
+ setImmediate(() => {
123
+ try {
124
+ this._nodes.rest.updatePlayer(payload)
125
+ } catch (error) {
126
+ if (!error.message.includes('ECONNREFUSED')) {
127
+ this._aqua.emit('debug', `[Player ${this._guildId}] Update failed: ${error.message}`)
128
+ }
129
+ }
130
+ })
128
131
  }
129
132
  }
130
133
 
@@ -1,171 +1,182 @@
1
- "use strict";
1
+ 'use strict'
2
2
 
3
3
  class Filters {
4
- constructor(player, options = {}) {
5
- this.player = player;
6
-
7
- this.defaults = {
8
- karaoke: { level: 1.0, monoLevel: 1.0, filterBand: 220.0, filterWidth: 100.0 },
9
- timescale: { speed: 1.0, pitch: 1.0, rate: 1.0 },
10
- tremolo: { frequency: 2.0, depth: 0.5 },
11
- vibrato: { frequency: 2.0, depth: 0.5 },
12
- rotation: { rotationHz: 0.0 },
13
- distortion: { sinOffset: 0.0, sinScale: 1.0, cosOffset: 0.0, cosScale: 1.0, tanOffset: 0.0, tanScale: 1.0, offset: 0.0, scale: 1.0 },
14
- channelMix: { leftToLeft: 1.0, leftToRight: 0.0, rightToLeft: 0.0, rightToRight: 1.0 },
15
- lowPass: { smoothing: 20.0 }
16
- };
17
-
18
- this.filters = {
19
- volume: options.volume || 1,
20
- equalizer: options.equalizer || [],
21
- karaoke: options.karaoke || null,
22
- timescale: options.timescale || null,
23
- tremolo: options.tremolo || null,
24
- vibrato: options.vibrato || null,
25
- rotation: options.rotation || null,
26
- distortion: options.distortion || null,
27
- channelMix: options.channelMix || null,
28
- lowPass: options.lowPass || null
29
- };
30
-
31
- this.presets = {
32
- bassboost: options.bassboost !== undefined ? options.bassboost : null,
33
- slowmode: options.slowmode !== undefined ? options.slowmode : null,
34
- nightcore: options.nightcore !== undefined ? options.nightcore : null,
35
- vaporwave: options.vaporwave !== undefined ? options.vaporwave : null,
36
- _8d: options._8d !== undefined ? options._8d : null
37
- };
38
-
39
- this._pendingUpdate = false;
40
- this._updateTimeout = null;
41
- }
42
-
43
- _setFilter(filterName, enabled, options = {}, defaultKey = filterName) {
44
- this.filters[filterName] = enabled ? { ...this.defaults[defaultKey], ...options } : null;
45
- return this._scheduleUpdate();
46
- }
47
-
48
- _scheduleUpdate() {
49
- this._pendingUpdate = true;
50
-
51
- if (this._updateTimeout) {
52
- clearTimeout(this._updateTimeout);
53
- }
54
-
55
- this._updateTimeout = setTimeout(() => this.updateFilters(), 0);
56
- return this;
57
- }
58
-
59
- setEqualizer(bands) {
60
- this.filters.equalizer = bands || [];
61
- return this._scheduleUpdate();
62
- }
63
-
64
- setKaraoke(enabled, options = {}) {
65
- return this._setFilter('karaoke', enabled, options);
66
- }
67
-
68
- setTimescale(enabled, options = {}) {
69
- return this._setFilter('timescale', enabled, options);
70
- }
71
-
72
- setTremolo(enabled, options = {}) {
73
- return this._setFilter('tremolo', enabled, options);
74
- }
75
-
76
- setVibrato(enabled, options = {}) {
77
- return this._setFilter('vibrato', enabled, options);
78
- }
79
-
80
- setRotation(enabled, options = {}) {
81
- return this._setFilter('rotation', enabled, options);
82
- }
83
-
84
- setDistortion(enabled, options = {}) {
85
- return this._setFilter('distortion', enabled, options);
86
- }
87
-
88
- setChannelMix(enabled, options = {}) {
89
- return this._setFilter('channelMix', enabled, options);
90
- }
91
-
92
- setLowPass(enabled, options = {}) {
93
- return this._setFilter('lowPass', enabled, options);
94
- }
95
-
96
- setBassboost(enabled, options = {}) {
97
- if (!enabled) {
98
- this.presets.bassboost = null;
99
- return this.setEqualizer([]);
100
- }
101
-
102
- const value = options.value || 5;
103
- if (value < 0 || value > 5) throw new Error("Bassboost value must be between 0 and 5");
104
-
105
- this.presets.bassboost = value;
106
- const gain = (value - 1) * (1.25 / 9) - 0.25;
107
-
108
- const eq = Array.from({ length: 13 }, (_, i) => ({ band: i, gain }));
109
-
110
- return this.setEqualizer(eq);
111
- }
112
-
113
- setSlowmode(enabled, options = {}) {
114
- this.presets.slowmode = enabled;
115
- return this.setTimescale(enabled, { rate: enabled ? (options.rate || 0.8) : 1.0 });
116
- }
117
-
118
- setNightcore(enabled, options = {}) {
119
- this.presets.nightcore = enabled;
120
- return this.setTimescale(enabled, { rate: enabled ? (options.rate || 1.5) : 1.0 });
121
- }
122
-
123
- setVaporwave(enabled, options = {}) {
124
- this.presets.vaporwave = enabled;
125
- return this.setTimescale(enabled, { pitch: enabled ? (options.pitch || 0.5) : 1.0 });
126
- }
127
-
128
- set8D(enabled, options = {}) {
129
- this.presets._8d = enabled;
130
- return this.setRotation(enabled, { rotationHz: enabled ? (options.rotationHz || 0.2) : 0.0 });
131
- }
132
-
133
- async clearFilters() {
134
- Object.keys(this.filters).forEach(key => {
135
- this.filters[key] = key === 'volume' ? 1 : (key === 'equalizer' ? [] : null);
136
- });
137
-
138
- Object.keys(this.presets).forEach(key => {
139
- this.presets[key] = null;
140
- });
141
-
142
- this._pendingUpdate = false;
143
- if (this._updateTimeout) {
144
- clearTimeout(this._updateTimeout);
145
- this._updateTimeout = null;
146
- }
147
-
148
- await this.updateFilters();
149
- return this;
150
- }
151
-
152
- async updateFilters() {
153
- if (!this._pendingUpdate && this._updateTimeout) {
154
- clearTimeout(this._updateTimeout);
155
- this._updateTimeout = null;
156
- return this;
157
- }
158
-
159
- this._pendingUpdate = false;
160
- this._updateTimeout = null;
161
-
162
- await this.player.nodes.rest.updatePlayer({
163
- guildId: this.player.guildId,
164
- data: { filters: { ...this.filters } }
165
- });
166
-
167
- return this;
168
- }
4
+ static defaults = {
5
+ karaoke: { level: 1, monoLevel: 1, filterBand: 220, filterWidth: 100 },
6
+ timescale: { speed: 1, pitch: 1, rate: 1 },
7
+ tremolo: { frequency: 2, depth: 0.5 },
8
+ vibrato: { frequency: 2, depth: 0.5 },
9
+ rotation: { rotationHz: 0 },
10
+ distortion: { sinOffset: 0, sinScale: 1, cosOffset: 0, cosScale: 1, tanOffset: 0, tanScale: 1, offset: 0, scale: 1 },
11
+ channelMix: { leftToLeft: 1, leftToRight: 0, rightToLeft: 0, rightToRight: 1 },
12
+ lowPass: { smoothing: 20 }
13
+ }
14
+
15
+ constructor(player, options = {}) {
16
+ this.player = player
17
+ this._pendingUpdate = false
18
+
19
+ this.filters = {
20
+ volume: options.volume ?? 1,
21
+ equalizer: options.equalizer ?? [],
22
+ karaoke: options.karaoke ?? null,
23
+ timescale: options.timescale ?? null,
24
+ tremolo: options.tremolo ?? null,
25
+ vibrato: options.vibrato ?? null,
26
+ rotation: options.rotation ?? null,
27
+ distortion: options.distortion ?? null,
28
+ channelMix: options.channelMix ?? null,
29
+ lowPass: options.lowPass ?? null
30
+ }
31
+
32
+ this.presets = {
33
+ bassboost: options.bassboost ?? null,
34
+ slowmode: options.slowmode ?? null,
35
+ nightcore: options.nightcore ?? null,
36
+ vaporwave: options.vaporwave ?? null,
37
+ _8d: options._8d ?? null
38
+ }
39
+ }
40
+
41
+ _setFilter(filterName, enabled, options = {}) {
42
+ const filter = enabled ? { ...Filters.defaults[filterName], ...options } : null
43
+ if (this.filters[filterName] === filter) return this
44
+
45
+ this.filters[filterName] = filter
46
+ return this._scheduleUpdate()
47
+ }
48
+
49
+ _scheduleUpdate() {
50
+ if (this._pendingUpdate) return this
51
+ this._pendingUpdate = true
52
+
53
+ queueMicrotask(() => {
54
+ this._pendingUpdate = false
55
+ this.updateFilters()
56
+ })
57
+
58
+ return this
59
+ }
60
+
61
+ setEqualizer(bands) {
62
+ if (this.filters.equalizer === bands) return this
63
+ this.filters.equalizer = bands || []
64
+ return this._scheduleUpdate()
65
+ }
66
+
67
+ setKaraoke(enabled, options = {}) {
68
+ return this._setFilter('karaoke', enabled, options)
69
+ }
70
+
71
+ setTimescale(enabled, options = {}) {
72
+ return this._setFilter('timescale', enabled, options)
73
+ }
74
+
75
+ setTremolo(enabled, options = {}) {
76
+ return this._setFilter('tremolo', enabled, options)
77
+ }
78
+
79
+ setVibrato(enabled, options = {}) {
80
+ return this._setFilter('vibrato', enabled, options)
81
+ }
82
+
83
+ setRotation(enabled, options = {}) {
84
+ return this._setFilter('rotation', enabled, options)
85
+ }
86
+
87
+ setDistortion(enabled, options = {}) {
88
+ return this._setFilter('distortion', enabled, options)
89
+ }
90
+
91
+ setChannelMix(enabled, options = {}) {
92
+ return this._setFilter('channelMix', enabled, options)
93
+ }
94
+
95
+ setLowPass(enabled, options = {}) {
96
+ return this._setFilter('lowPass', enabled, options)
97
+ }
98
+
99
+ setBassboost(enabled, options = {}) {
100
+ if (!enabled) {
101
+ if (this.presets.bassboost === null) return this
102
+ this.presets.bassboost = null
103
+ return this.setEqualizer([])
104
+ }
105
+
106
+ const value = options.value ?? 5
107
+ if (value < 0 || value > 5) throw new Error('Bassboost value must be between 0 and 5')
108
+ if (this.presets.bassboost === value) return this
109
+
110
+ this.presets.bassboost = value
111
+ const gain = (value - 1) * (1.25 / 9) - 0.25
112
+ const eq = Array(13).fill().map((_, band) => ({ band, gain }))
113
+
114
+ return this.setEqualizer(eq)
115
+ }
116
+
117
+ setSlowmode(enabled, options = {}) {
118
+ const rate = enabled ? options.rate ?? 0.8 : 1
119
+ if (this.presets.slowmode === enabled && this.filters.timescale?.rate === rate) return this
120
+
121
+ this.presets.slowmode = enabled
122
+ return this.setTimescale(enabled, { rate })
123
+ }
124
+
125
+ setNightcore(enabled, options = {}) {
126
+ const rate = enabled ? options.rate ?? 1.5 : 1
127
+ if (this.presets.nightcore === enabled && this.filters.timescale?.rate === rate) return this
128
+
129
+ this.presets.nightcore = enabled
130
+ return this.setTimescale(enabled, { rate })
131
+ }
132
+
133
+ setVaporwave(enabled, options = {}) {
134
+ const pitch = enabled ? options.pitch ?? 0.5 : 1
135
+ if (this.presets.vaporwave === enabled && this.filters.timescale?.pitch === pitch) return this
136
+
137
+ this.presets.vaporwave = enabled
138
+ return this.setTimescale(enabled, { pitch })
139
+ }
140
+
141
+ set8D(enabled, options = {}) {
142
+ const rotationHz = enabled ? options.rotationHz ?? 0.2 : 0
143
+ if (this.presets._8d === enabled && this.filters.rotation?.rotationHz === rotationHz) return this
144
+
145
+ this.presets._8d = enabled
146
+ return this.setRotation(enabled, { rotationHz })
147
+ }
148
+
149
+ async clearFilters() {
150
+ let needsUpdate = false
151
+
152
+ // Reset filters
153
+ Object.keys(this.filters).forEach(key => {
154
+ const newValue = key === 'volume' ? 1 : key === 'equalizer' ? [] : null
155
+ if (this.filters[key] !== newValue) {
156
+ this.filters[key] = newValue
157
+ needsUpdate = true
158
+ }
159
+ })
160
+
161
+ // Reset presets
162
+ Object.keys(this.presets).forEach(key => {
163
+ if (this.presets[key] !== null) {
164
+ this.presets[key] = null
165
+ }
166
+ })
167
+
168
+ if (!needsUpdate) return this
169
+ return this.updateFilters()
170
+ }
171
+
172
+ async updateFilters() {
173
+ await this.player.nodes.rest.updatePlayer({
174
+ guildId: this.player.guildId,
175
+ data: { filters: this.filters }
176
+ })
177
+
178
+ return this
179
+ }
169
180
  }
170
181
 
171
- module.exports = Filters;
182
+ module.exports = Filters