aqualink 2.11.8 → 2.11.10

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,111 +1,121 @@
1
- const https = require('https');
2
- const crypto = require('crypto');
1
+ const https = require('https')
2
+
3
+ const AGENT_CONFIG = {
4
+ keepAlive: true,
5
+ maxSockets: 5,
6
+ maxFreeSockets: 2,
7
+ timeout: 8000,
8
+ freeSocketTimeout: 4000
9
+ }
10
+
11
+ const agent = new https.Agent(AGENT_CONFIG)
3
12
 
4
- const agent = new https.Agent({
5
- keepAlive: true,
6
- maxSockets: 5,
7
- maxFreeSockets: 2,
8
- timeout: 8000,
9
- freeSocketTimeout: 4000
10
- });
13
+ const SC_LINK_RE = /<a\s+itemprop="url"\s+href="(\/[^"]+)"/g
14
+ const MAX_REDIRECTS = 3
15
+ const MAX_RESPONSE_BYTES = 5 * 1024 * 1024 // 5 MB
16
+ const MAX_SC_LINKS = 50
17
+ const MAX_SP_RESULTS = 5
18
+ const DEFAULT_TIMEOUT_MS = 8000
11
19
 
20
+ const fastFetch = (url, depth = 0) => new Promise((resolve, reject) => {
21
+ if (depth > MAX_REDIRECTS) return reject(new Error('Too many redirects'))
12
22
 
13
- const SOUNDCLOUD_REGEX = /<a\s+itemprop="url"\s+href="(\/[^"]+)"/g;
23
+ const req = https.get(url, { agent, timeout: DEFAULT_TIMEOUT_MS }, res => {
24
+ const { statusCode, headers } = res
14
25
 
15
- const shuffleArray = (arr) => {
16
- for (let i = arr.length - 1; i > 0; i--) {
17
- const j = Math.random() * (i + 1) | 0;
18
- [arr[i], arr[j]] = [arr[j], arr[i]];
26
+ if (statusCode >= 300 && statusCode < 400 && headers.location) {
27
+ res.resume()
28
+ return fastFetch(new URL(headers.location, url).href, depth + 1).then(resolve, reject)
19
29
  }
20
- return arr;
21
- };
22
-
23
- const fastFetch = (url, options = {}) => {
24
- return new Promise((resolve, reject) => {
25
- const req = https.get(url, { ...options, agent }, (res) => {
26
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
27
- res.resume();
28
- return fastFetch(new URL(res.headers.location, url).href, options)
29
- .then(resolve, reject);
30
- }
31
-
32
- if (res.statusCode !== 200) {
33
- res.resume();
34
- return reject(new Error(`HTTP ${res.statusCode}`));
35
- }
36
-
37
- const chunks = [];
38
- res.on('data', chunk => chunks.push(chunk));
39
- res.on('end', () => resolve(Buffer.concat(chunks).toString()));
40
- });
41
-
42
- req.on('error', reject);
43
- req.setTimeout(8000, () => req.destroy(new Error('Timeout')));
44
- });
45
- };
46
-
47
- const soundAutoPlay = async (baseUrl) => {
48
- try {
49
- const html = await fastFetch(`${baseUrl}/recommended`);
50
-
51
- const links = [];
52
- let match;
53
- while ((match = SOUNDCLOUD_REGEX.exec(html)) && links.length < 50) {
54
- links.push(`https://soundcloud.com${match[1]}`);
55
- }
56
-
57
- if (!links.length) throw new Error("No tracks found");
58
-
59
- return shuffleArray(links);
60
- } catch (err) {
61
- console.error("SoundCloud error:", err.message);
62
- return [];
30
+
31
+ if (statusCode !== 200) {
32
+ res.resume()
33
+ return reject(new Error(`HTTP ${statusCode}`))
63
34
  }
64
- };
65
35
 
66
- const spotifyAutoPlay = async (seed, player, requester, excludedIdentifiers = []) => {
67
- try {
68
- const { trackId, artistIds } = seed
69
- if (!trackId) return null
70
-
71
- const prevIdentifier = player.current?.identifier
72
- let seedQuery = `seed_tracks=${trackId}`
73
- if (artistIds) seedQuery += `&seed_artists=${artistIds}`
74
- console.log('Seed query:', seedQuery)
75
-
76
- const response = await player.aqua.resolve({
77
- query: seedQuery,
78
- source: 'spsearch',
79
- requester
36
+ const chunks = []
37
+ let received = 0
38
+
39
+ res.on('data', chunk => {
40
+ received += chunk.length
41
+ if (received > MAX_RESPONSE_BYTES) {
42
+ req.destroy(new Error('Response too large'))
43
+ return
44
+ }
45
+ chunks.push(chunk)
80
46
  })
81
- const candidates = response?.tracks || []
82
-
83
- const seenIds = new Set(excludedIdentifiers)
84
- if (prevIdentifier) seenIds.add(prevIdentifier)
85
-
86
- const result = []
87
- for (const track of candidates) {
88
- const { identifier } = track
89
- if (seenIds.has(identifier)) continue
90
- seenIds.add(identifier)
91
- track.pluginInfo = {
92
- ...(track.pluginInfo || {}),
93
- clientData: { fromAutoplay: true }
47
+
48
+ res.on('end', () => {
49
+ try {
50
+ const buf = Buffer.concat(chunks)
51
+ resolve(buf.toString())
52
+ } catch (err) {
53
+ reject(err)
94
54
  }
95
- result.push(track)
96
- if (result.length === 5) break
55
+ })
56
+ })
57
+
58
+ req.on('error', reject)
59
+ req.setTimeout(DEFAULT_TIMEOUT_MS, () => req.destroy(new Error('Timeout')))
60
+ })
61
+
62
+ const shuffleInPlace = arr => {
63
+ for (let i = arr.length - 1; i > 0; i--) {
64
+ const j = Math.random() * (i + 1) | 0
65
+ const tmp = arr[i]
66
+ arr[i] = arr[j]
67
+ arr[j] = tmp
68
+ }
69
+ return arr
70
+ }
71
+
72
+ const scAutoPlay = async baseUrl => {
73
+ try {
74
+ const html = await fastFetch(`${baseUrl}/recommended`)
75
+ const links = []
76
+ for (const m of html.matchAll(SC_LINK_RE)) {
77
+ if (!m[1]) continue
78
+ links.push(`https://soundcloud.com${m[1]}`)
79
+ if (links.length >= MAX_SC_LINKS) break
97
80
  }
81
+ return links.length ? shuffleInPlace(links) : []
82
+ } catch (err) {
83
+ console.error('scAutoPlay error:', err?.message || err)
84
+ return []
85
+ }
86
+ }
87
+
88
+ const spAutoPlay = async (seed, player, requester, excludedIds = []) => {
89
+ try {
90
+ if (!seed?.trackId) return null
91
+
92
+ const seedQuery = `seed_tracks=${seed.trackId}${seed.artistIds ? `&seed_artists=${seed.artistIds}` : ''}`
93
+ const res = await player.aqua.resolve({ query: seedQuery, source: 'spsearch', requester })
98
94
 
99
- return result
95
+ const candidates = res?.tracks || []
96
+ if (!candidates.length) return null
100
97
 
98
+ const seen = new Set(excludedIds)
99
+ const prevId = player.current?.identifier
100
+ if (prevId) seen.add(prevId)
101
+
102
+ const out = []
103
+ for (const t of candidates) {
104
+ if (seen.has(t.identifier)) continue
105
+ seen.add(t.identifier)
106
+ t.pluginInfo = { ...(t.pluginInfo || {}), clientData: { fromAutoplay: true } }
107
+ out.push(t)
108
+ if (out.length === MAX_SP_RESULTS) break
109
+ }
110
+
111
+ return out.length ? out : null
101
112
  } catch (err) {
102
- console.error('Spotify autoplay error:', err)
113
+ console.error('spAutoPlay error:', err)
103
114
  return null
104
115
  }
105
116
  }
106
117
 
107
-
108
118
  module.exports = {
109
- scAutoPlay: soundAutoPlay,
110
- spAutoPlay: spotifyAutoPlay
111
- };
119
+ scAutoPlay,
120
+ spAutoPlay
121
+ }