aqualink 3.0.0 → 3.2.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.
@@ -11,10 +11,7 @@ const {
11
11
  zstdDecompressSync
12
12
  } = require('node:zlib')
13
13
 
14
- let autoplayModule = null
15
- try {
16
- autoplayModule = require('../handlers/autoplay')
17
- } catch {}
14
+ const { emitOperationalError } = require('./Reporting')
18
15
 
19
16
  const unrefTimer = (t) => {
20
17
  try {
@@ -90,7 +87,6 @@ const _functions = {
90
87
  parseBody(data, contentType, forceJson) {
91
88
  const isJson = forceJson || this.isJsonContent(contentType)
92
89
  if (isJson) {
93
- if (typeof data === 'string') return JSON.parse(data)
94
90
  return JSON.parse(data)
95
91
  }
96
92
  return typeof data === 'string' ? data : data.toString(UTF8)
@@ -134,6 +130,7 @@ class Rest {
134
130
  this.aqua = aqua
135
131
  this.node = node
136
132
  this.sessionId = node.sessionId
133
+ this._sessionGeneration = 0
137
134
  this.timeout = node.timeout || 30000
138
135
 
139
136
  const protocol = node.ssl ? 'https:' : 'http:'
@@ -208,21 +205,18 @@ class Rest {
208
205
  this.agent = new (node.ssl ? HttpsAgent : HttpAgent)(opts)
209
206
  this.request = node.ssl ? httpsRequest : httpRequest
210
207
 
211
- if (autoplayModule?.setSharedAgent) {
212
- if (node.ssl) {
213
- this._autoplayAgent = this.agent
214
- } else {
215
- this._autoplayAgent = new HttpsAgent({
216
- keepAlive: true,
217
- maxSockets: node.maxSockets || 128,
218
- maxFreeSockets: node.maxFreeSockets || 64,
219
- freeSocketTimeout: node.freeSocketTimeout || 15000,
220
- keepAliveMsecs: node.keepAliveMsecs || 500,
221
- scheduling: 'lifo',
222
- timeout: this.timeout
223
- })
224
- }
225
- autoplayModule.setSharedAgent(this._autoplayAgent)
208
+ if (node.ssl) {
209
+ this._autoplayAgent = this.agent
210
+ } else {
211
+ this._autoplayAgent = new HttpsAgent({
212
+ keepAlive: true,
213
+ maxSockets: node.maxSockets || 128,
214
+ maxFreeSockets: node.maxFreeSockets || 64,
215
+ freeSocketTimeout: node.freeSocketTimeout || 15000,
216
+ keepAliveMsecs: node.keepAliveMsecs || 500,
217
+ scheduling: 'lifo',
218
+ timeout: this.timeout
219
+ })
226
220
  }
227
221
 
228
222
  const origCreate = this.agent.createConnection.bind(this.agent)
@@ -236,10 +230,18 @@ class Rest {
236
230
 
237
231
  setSessionId(sessionId) {
238
232
  this.sessionId = sessionId
233
+ this._sessionGeneration++
239
234
  }
240
235
 
241
- _getSessionPath() {
236
+ _getSessionPath(generation) {
242
237
  if (!this.sessionId) throw ERRORS.NO_SESSION
238
+ if (generation != null && generation !== this._sessionGeneration) {
239
+ const staleErr = new Error(
240
+ `Stale session: sessionId was updated (expected gen ${generation}, current ${this._sessionGeneration}) for session ${this.sessionId}`
241
+ )
242
+ staleErr.statusCode = 404
243
+ throw staleErr
244
+ }
243
245
  return `${this._apiBase}/sessions/${this.sessionId}`
244
246
  }
245
247
 
@@ -271,7 +273,7 @@ class Rest {
271
273
  }
272
274
  }
273
275
 
274
- _collectBody(stream, preallocSize = 0) {
276
+ _collectBody(stream, preallocSize = 0, source = stream) {
275
277
  return new Promise((resolve, reject) => {
276
278
  let done = false
277
279
  let size = 0
@@ -304,7 +306,11 @@ class Rest {
304
306
  size += chunk.length
305
307
  chunks.push(chunk)
306
308
  }
307
- if (size > MAX_RESPONSE_SIZE) finish(false, ERRORS.RESPONSE_TOO_LARGE)
309
+ if (size > MAX_RESPONSE_SIZE) {
310
+ stream.destroy?.(ERRORS.RESPONSE_TOO_LARGE)
311
+ if (source !== stream) source.destroy?.(ERRORS.RESPONSE_TOO_LARGE)
312
+ finish(false, ERRORS.RESPONSE_TOO_LARGE)
313
+ }
308
314
  })
309
315
 
310
316
  stream.once('error', (e) => finish(false, e))
@@ -414,7 +420,7 @@ class Rest {
414
420
 
415
421
  const clInt = cl ? parseInt(cl, 10) : 0
416
422
  if (clInt > MAX_RESPONSE_SIZE) {
417
- res.resume()
423
+ res.destroy(ERRORS.RESPONSE_TOO_LARGE)
418
424
  return finish(false, ERRORS.RESPONSE_TOO_LARGE)
419
425
  }
420
426
 
@@ -472,7 +478,7 @@ class Rest {
472
478
  preallocSize = clInt
473
479
  }
474
480
 
475
- this._collectBody(stream, preallocSize)
481
+ this._collectBody(stream, preallocSize, res)
476
482
  .then((buffer) => {
477
483
  if (!buffer) return finish(true, null)
478
484
  finalize(buffer)
@@ -482,6 +488,10 @@ class Rest {
482
488
  )
483
489
 
484
490
  req.once('error', (e) => finish(false, e))
491
+ req.setTimeout(this.timeout, () => {
492
+ req.destroy()
493
+ finish(false, new Error(`Socket timeout: ${this.timeout}ms`))
494
+ })
485
495
  timer = setTimeout(
486
496
  () => finish(false, new Error(`Request timeout: ${this.timeout}ms`)),
487
497
  this.timeout
@@ -494,13 +504,14 @@ class Rest {
494
504
  _getH2Session() {
495
505
  if (!this._h2 || this._h2.closed || this._h2.destroyed) {
496
506
  this._clearH2()
497
- this._h2 = http2.connect(this.baseUrl, this._tlsOptions || undefined)
507
+ const session = http2.connect(this.baseUrl, this._tlsOptions || undefined)
508
+ this._h2 = session
498
509
  this._resetH2Timer()
499
510
 
500
- const onEnd = () => this._clearH2()
501
- this._h2.once('error', onEnd)
502
- this._h2.once('close', onEnd)
503
- this._h2.socket?.unref?.()
511
+ const onEnd = () => this._clearH2(session)
512
+ session.once('error', onEnd)
513
+ session.once('close', onEnd)
514
+ session.socket?.unref?.()
504
515
  }
505
516
  return this._h2
506
517
  }
@@ -511,12 +522,14 @@ class Rest {
511
522
  this._h2Timer = null
512
523
  }
513
524
  if (this._h2 && !this._h2.closed && !this._h2.destroyed) {
514
- this._h2Timer = setTimeout(() => this._closeH2(), H2_TIMEOUT)
525
+ const session = this._h2
526
+ this._h2Timer = setTimeout(() => this._closeH2(session), H2_TIMEOUT)
515
527
  unrefTimer(this._h2Timer)
516
528
  }
517
529
  }
518
530
 
519
- _clearH2() {
531
+ _clearH2(session = this._h2) {
532
+ if (session !== this._h2) return
520
533
  if (this._h2Timer) {
521
534
  clearTimeout(this._h2Timer)
522
535
  this._h2Timer = null
@@ -524,7 +537,8 @@ class Rest {
524
537
  this._h2 = null
525
538
  }
526
539
 
527
- _closeH2() {
540
+ _closeH2(session = this._h2) {
541
+ if (session !== this._h2) return
528
542
  if (this._h2Timer) {
529
543
  clearTimeout(this._h2Timer)
530
544
  this._h2Timer = null
@@ -588,7 +602,7 @@ class Rest {
588
602
 
589
603
  const clInt = cl ? parseInt(cl, 10) : 0
590
604
  if (clInt > MAX_RESPONSE_SIZE) {
591
- req.resume()
605
+ req.close(http2.constants.NGHTTP2_CANCEL)
592
606
  return finish(false, ERRORS.RESPONSE_TOO_LARGE)
593
607
  }
594
608
 
@@ -621,7 +635,7 @@ class Rest {
621
635
 
622
636
  if (decomp) decomp.once('error', (e) => finish(false, e))
623
637
  req.once('error', (e) => finish(false, e))
624
- this._collectBody(stream, preallocSize)
638
+ this._collectBody(stream, preallocSize, req)
625
639
  .then((buffer) => {
626
640
  if (!buffer) return finish(true, null)
627
641
  finalize(buffer)
@@ -639,28 +653,33 @@ class Rest {
639
653
  }
640
654
 
641
655
  async updatePlayer({ guildId, data, noReplace = false }) {
656
+ const gen = this._sessionGeneration
642
657
  return this.makeRequest(
643
658
  'PATCH',
644
- `${this._getSessionPath()}/players/${guildId}?noReplace=${noReplace}`,
659
+ `${this._getSessionPath(gen)}/players/${guildId}?noReplace=${noReplace}`,
645
660
  data
646
661
  )
647
662
  }
648
663
 
649
664
  async getPlayer(guildId) {
665
+ const gen = this._sessionGeneration
650
666
  return this.makeRequest(
651
667
  'GET',
652
- `${this._getSessionPath()}/players/${guildId}`
668
+ `${this._getSessionPath(gen)}/players/${guildId}`
653
669
  )
654
670
  }
655
671
 
656
672
  async getPlayers() {
657
- return this.makeRequest('GET', `${this._getSessionPath()}/players`)
673
+ const gen = this._sessionGeneration
674
+ return this.makeRequest('GET', `${this._getSessionPath(gen)}/players`)
658
675
  }
659
676
 
660
- async destroyPlayer(guildId) {
677
+ async destroyPlayer(guildId, abortSignal) {
678
+ const gen = this._sessionGeneration
679
+ if (abortSignal?.aborted) return null
661
680
  return this.makeRequest(
662
681
  'DELETE',
663
- `${this._getSessionPath()}/players/${guildId}`
682
+ `${this._getSessionPath(gen)}/players/${guildId}`
664
683
  )
665
684
  }
666
685
 
@@ -725,7 +744,7 @@ class Rest {
725
744
  const title = track?.info?.title
726
745
 
727
746
  if (!track || (!guildId && !hasEncoded && !title)) {
728
- this.aqua?.emit?.('error', '[Aqua/Lyrics] Invalid track object')
747
+ emitOperationalError(this.aqua, this.node, 'Invalid lyrics track object')
729
748
  return null
730
749
  }
731
750
 
@@ -733,9 +752,10 @@ class Rest {
733
752
 
734
753
  if (guildId) {
735
754
  try {
755
+ const gen = this._sessionGeneration
736
756
  const lyrics = await this.makeRequest(
737
757
  'GET',
738
- `${this._getSessionPath()}/players/${guildId}/track/lyrics?skipTrackSource=${skip}`
758
+ `${this._getSessionPath(gen)}/players/${guildId}/track/lyrics?skipTrackSource=${skip}`
739
759
  )
740
760
  if (this._validLyrics(lyrics)) return lyrics
741
761
  } catch {}
@@ -776,10 +796,11 @@ class Rest {
776
796
 
777
797
  async subscribeLiveLyrics(guildId, skipTrackSource = false) {
778
798
  try {
799
+ const gen = this._sessionGeneration
779
800
  return (
780
801
  (await this.makeRequest(
781
802
  'POST',
782
- `${this._getSessionPath()}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource ? 'true' : 'false'}`
803
+ `${this._getSessionPath(gen)}/players/${guildId}/lyrics/subscribe?skipTrackSource=${skipTrackSource ? 'true' : 'false'}`
783
804
  )) === null
784
805
  )
785
806
  } catch {
@@ -789,10 +810,11 @@ class Rest {
789
810
 
790
811
  async unsubscribeLiveLyrics(guildId) {
791
812
  try {
813
+ const gen = this._sessionGeneration
792
814
  return (
793
815
  (await this.makeRequest(
794
816
  'DELETE',
795
- `${this._getSessionPath()}/players/${guildId}/lyrics/subscribe`
817
+ `${this._getSessionPath(gen)}/players/${guildId}/lyrics/subscribe`
796
818
  )) === null
797
819
  )
798
820
  } catch {
@@ -816,9 +838,10 @@ class Rest {
816
838
  volume: options.volume !== undefined ? options.volume : 0.8
817
839
  }
818
840
 
841
+ const gen = this._sessionGeneration
819
842
  return this.makeRequest(
820
843
  'POST',
821
- `/v4/sessions/${this.sessionId}/players/${guildId}/mix`,
844
+ `${this._getSessionPath(gen)}/players/${guildId}/mix`,
822
845
  payload
823
846
  )
824
847
  }
@@ -826,9 +849,10 @@ class Rest {
826
849
  async getActiveMixer(guildId) {
827
850
  if (!this.node.isNodelink)
828
851
  throw new Error('Mixer endpoints are only available on Nodelink nodes')
852
+ const gen = this._sessionGeneration
829
853
  const response = await this.makeRequest(
830
854
  'GET',
831
- `/v4/sessions/${this.sessionId}/players/${guildId}/mix`
855
+ `${this._getSessionPath(gen)}/players/${guildId}/mix`
832
856
  )
833
857
  return response?.mixes || []
834
858
  }
@@ -839,9 +863,10 @@ class Rest {
839
863
  if (!guildId || !mix || typeof volume !== 'number')
840
864
  throw new Error('You forget to set the guild_id, mix or volume options')
841
865
 
866
+ const gen = this._sessionGeneration
842
867
  return this.makeRequest(
843
868
  'PATCH',
844
- `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`,
869
+ `${this._getSessionPath(gen)}/players/${guildId}/mix/${mix}`,
845
870
  { volume }
846
871
  )
847
872
  }
@@ -852,9 +877,10 @@ class Rest {
852
877
  if (!guildId || !mix)
853
878
  throw new Error('You forget to set the guild_id and/or mix options')
854
879
 
880
+ const gen = this._sessionGeneration
855
881
  return this.makeRequest(
856
882
  'DELETE',
857
- `/v4/sessions/${this.sessionId}/players/${guildId}/mix/${mix}`
883
+ `${this._getSessionPath(gen)}/players/${guildId}/mix/${mix}`
858
884
  )
859
885
  }
860
886
 
@@ -868,9 +894,6 @@ class Rest {
868
894
  if (autoplayAgent && autoplayAgent !== primaryAgent) {
869
895
  autoplayAgent.destroy?.()
870
896
  }
871
- if (autoplayModule?.setSharedAgent && autoplayAgent) {
872
- autoplayModule.setSharedAgent(null)
873
- }
874
897
  this._closeH2()
875
898
  if (this._headerPool) {
876
899
  this._headerPool.length = 0
@@ -9,6 +9,7 @@ const _h = {
9
9
  class Track {
10
10
  constructor(data = {}, requester = null, node = null) {
11
11
  const info = data.info || {}
12
+ const pluginInfo = info.pluginInfo || data.pluginInfo || {}
12
13
 
13
14
  this.track = data.track || data.encoded || null
14
15
  this.identifier = _h.str(info.identifier)
@@ -21,14 +22,15 @@ class Track {
21
22
  this.uri = _h.str(info.uri)
22
23
  this.sourceName = _h.str(info.sourceName)
23
24
  this.artworkUrl = _h.str(info.artworkUrl)
24
- this.pluginInfo = info.pluginInfo || data.pluginInfo || {}
25
+ this.pluginInfo = pluginInfo
26
+ this.isrc = _h.str(info.isrc || pluginInfo.isrc) // ISRC support
25
27
 
26
28
  this.playlist = data.playlist || null
27
29
  this.node = node || data.node || null
28
30
  this.nodes = data.nodes || null
29
31
  this.requester = requester || null
30
32
  this._infoCache = null
31
- this._artworkCache = undefined // undefined = not computed, null = computed but no artwork
33
+ this._artworkCache = undefined
32
34
  }
33
35
 
34
36
  get info() {
@@ -43,7 +45,8 @@ class Track {
43
45
  title: this.title,
44
46
  uri: this.uri,
45
47
  sourceName: this.sourceName,
46
- artworkUrl: this.artworkUrl || this._computeArtwork()
48
+ artworkUrl: this.artworkUrl || this._computeArtwork(),
49
+ isrc: this.isrc || null // exposed here
47
50
  })
48
51
  return this._infoCache
49
52
  }
@@ -113,6 +116,9 @@ class Track {
113
116
  this.position = _h.num(fi.position, this.position)
114
117
  this.duration = _h.num(fi.length, this.duration)
115
118
  this.playlist = found.playlist ?? this.playlist
119
+ this.isrc = _h.str(
120
+ fi.isrc ?? fi.pluginInfo?.isrc ?? found.pluginInfo?.isrc ?? this.isrc
121
+ ) // keep ISRC
116
122
  this._infoCache = null
117
123
 
118
124
  return this
@@ -139,6 +145,7 @@ class Track {
139
145
  this.uri =
140
146
  this.sourceName =
141
147
  this.artworkUrl =
148
+ this.isrc =
142
149
  ''
143
150
  }
144
151
 
package/package.json CHANGED
@@ -1,70 +1,70 @@
1
- {
2
- "name": "aqualink",
3
- "version": "3.0.0",
4
- "description": "An Lavalink/Nodelink client, focused in pure performance and features",
5
- "main": "./build/index.js",
6
- "types": "./build/index.d.ts",
7
- "scripts": {
8
- "start": "node ./build/index.js"
9
- },
10
- "exports": {
11
- ".": {
12
- "require": "./build/index.js",
13
- "import": "./build/index.js",
14
- "types": "./build/index.d.ts"
15
- }
16
- },
17
- "keywords": [
18
- "lavalink",
19
- "lavalink-client",
20
- "client-wrapper",
21
- "discord.js",
22
- "node.js",
23
- "audio-streaming",
24
- "music-bot",
25
- "discord-integration",
26
- "high-performance",
27
- "scalable",
28
- "nodelink",
29
- "api",
30
- "discord",
31
- "lavalink.js",
32
- "discord.js",
33
- "lavalink-api",
34
- "cross-platform",
35
- "seamless-integration",
36
- "community-support",
37
- "open-source",
38
- "opus"
39
- ],
40
- "author": "mushroom0162 (https://github.com/ToddyTheNoobDud)",
41
- "license": "ISC",
42
- "repository": {
43
- "type": "git",
44
- "url": "git+https://github.com/ToddyTheNoobDud/AquaLink.git"
45
- },
46
- "homepage": "https://aqualink-6006388d.mintlify.app/",
47
- "dependencies": {
48
- "ws": "^8.19.0"
49
- },
50
- "contributors": [
51
- {
52
- "name": "mushroom0162",
53
- "url": "https://github.com/ToddyTheNoobDud"
54
- },
55
- {
56
- "name": "SoulDevs",
57
- "url": "https://github.com/SoulDevs"
58
- },
59
- {
60
- "name": "southctrl",
61
- "url": "https://github.com/southctrl"
62
- }
63
- ],
64
- "maintainers": [
65
- {
66
- "name": "mushroom0162",
67
- "url": "https://github.com/ToddyTheNoobDud"
68
- }
69
- ]
70
- }
1
+ {
2
+ "name": "aqualink",
3
+ "version": "3.2.0",
4
+ "description": "An Lavalink/Nodelink client, focused in pure performance and features",
5
+ "main": "./build/index.js",
6
+ "types": "./build/index.d.ts",
7
+ "scripts": {
8
+ "start": "node ./build/index.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "require": "./build/index.js",
13
+ "import": "./build/index.js",
14
+ "types": "./build/index.d.ts"
15
+ }
16
+ },
17
+ "keywords": [
18
+ "lavalink",
19
+ "lavalink-client",
20
+ "client-wrapper",
21
+ "discord.js",
22
+ "node.js",
23
+ "audio-streaming",
24
+ "music-bot",
25
+ "discord-integration",
26
+ "high-performance",
27
+ "scalable",
28
+ "nodelink",
29
+ "api",
30
+ "discord",
31
+ "lavalink.js",
32
+ "discord.js",
33
+ "lavalink-api",
34
+ "cross-platform",
35
+ "seamless-integration",
36
+ "community-support",
37
+ "open-source",
38
+ "opus"
39
+ ],
40
+ "author": "mushroom0162 (https://github.com/ToddyTheNoobDud)",
41
+ "license": "ISC",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/ToddyTheNoobDud/AquaLink.git"
45
+ },
46
+ "homepage": "https://aqualink-6006388d.mintlify.app/",
47
+ "dependencies": {
48
+ "ws": "^8.21.1"
49
+ },
50
+ "contributors": [
51
+ {
52
+ "name": "mushroom0162",
53
+ "url": "https://github.com/ToddyTheNoobDud"
54
+ },
55
+ {
56
+ "name": "SoulDevs",
57
+ "url": "https://github.com/SoulDevs"
58
+ },
59
+ {
60
+ "name": "southctrl",
61
+ "url": "https://github.com/southctrl"
62
+ }
63
+ ],
64
+ "maintainers": [
65
+ {
66
+ "name": "mushroom0162",
67
+ "url": "https://github.com/ToddyTheNoobDud"
68
+ }
69
+ ]
70
+ }