aqualink 2.6.1-fix → 2.6.1-fix3
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/build/handlers/autoplay.js +77 -105
- package/build/structures/Player.js +6 -7
- package/package.json +1 -1
|
@@ -1,142 +1,114 @@
|
|
|
1
1
|
const https = require('https');
|
|
2
2
|
const crypto = require('crypto');
|
|
3
3
|
|
|
4
|
-
const agent = new https.Agent({
|
|
4
|
+
const agent = new https.Agent({
|
|
5
|
+
keepAlive: true,
|
|
6
|
+
maxSockets: 5,
|
|
7
|
+
maxFreeSockets: 2,
|
|
8
|
+
timeout: 8000,
|
|
9
|
+
freeSocketTimeout: 4000
|
|
10
|
+
});
|
|
5
11
|
|
|
6
12
|
const TOTP_SECRET = Buffer.from("5507145853487499592248630329347", 'utf8');
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
15
|
-
if (redirectCount >= maxRedirects) {
|
|
16
|
-
return reject(new Error('Too many redirects'));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
res.resume();
|
|
20
|
-
try {
|
|
21
|
-
const resolved = await quickFetch(
|
|
22
|
-
new URL(res.headers.location, url).toString(),
|
|
23
|
-
options,
|
|
24
|
-
redirectCount + 1
|
|
25
|
-
);
|
|
26
|
-
resolve(resolved);
|
|
27
|
-
} catch (err) {
|
|
28
|
-
reject(err);
|
|
29
|
-
}
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (res.statusCode !== 200) {
|
|
34
|
-
res.resume();
|
|
35
|
-
return reject(new Error(`Request failed. Status code: ${res.statusCode}`));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const chunks = [];
|
|
39
|
-
let length = 0;
|
|
40
|
-
|
|
41
|
-
res.on('data', (chunk) => {
|
|
42
|
-
chunks.push(chunk);
|
|
43
|
-
length += chunk.length;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
res.on('end', () => {
|
|
47
|
-
resolve(Buffer.concat(chunks, length).toString());
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
req.on('error', reject);
|
|
52
|
-
req.setTimeout(10000, () => {
|
|
53
|
-
req.destroy(new Error('Request timeout'));
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
} catch (err) {
|
|
57
|
-
throw err;
|
|
14
|
+
const SOUNDCLOUD_REGEX = /<a\s+itemprop="url"\s+href="(\/[^"]+)"/g;
|
|
15
|
+
|
|
16
|
+
const shuffleArray = (arr) => {
|
|
17
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
18
|
+
const j = Math.random() * (i + 1) | 0;
|
|
19
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
58
20
|
}
|
|
59
|
-
|
|
21
|
+
return arr;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const fastFetch = (url, options = {}) => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const req = https.get(url, { ...options, agent }, (res) => {
|
|
27
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
28
|
+
res.resume();
|
|
29
|
+
return fastFetch(new URL(res.headers.location, url).href, options)
|
|
30
|
+
.then(resolve, reject);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (res.statusCode !== 200) {
|
|
34
|
+
res.resume();
|
|
35
|
+
return reject(new Error(`HTTP ${res.statusCode}`));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const chunks = [];
|
|
39
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
40
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString()));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
req.on('error', reject);
|
|
44
|
+
req.setTimeout(8000, () => req.destroy(new Error('Timeout')));
|
|
45
|
+
});
|
|
46
|
+
};
|
|
60
47
|
|
|
61
|
-
async
|
|
48
|
+
const soundAutoPlay = async (baseUrl) => {
|
|
62
49
|
try {
|
|
63
|
-
const html = await
|
|
64
|
-
const links = new Set();
|
|
65
|
-
const regex = /<a\s+itemprop="url"\s+href="(\/[^"]+)"/g;
|
|
50
|
+
const html = await fastFetch(`${baseUrl}/recommended`);
|
|
66
51
|
|
|
52
|
+
const links = [];
|
|
67
53
|
let match;
|
|
68
|
-
while ((match =
|
|
69
|
-
links.
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!links.size) {
|
|
73
|
-
throw new Error("No recommended tracks found on SoundCloud.");
|
|
54
|
+
while ((match = SOUNDCLOUD_REGEX.exec(html)) && links.length < 50) {
|
|
55
|
+
links.push(`https://soundcloud.com${match[1]}`);
|
|
74
56
|
}
|
|
75
57
|
|
|
76
|
-
|
|
77
|
-
for (let i = urls.length - 1; i > 0; i--) {
|
|
78
|
-
const j = Math.random() * (i + 1) | 0;
|
|
79
|
-
[urls[i], urls[j]] = [urls[j], urls[i]];
|
|
80
|
-
}
|
|
58
|
+
if (!links.length) throw new Error("No tracks found");
|
|
81
59
|
|
|
82
|
-
return
|
|
60
|
+
return shuffleArray(links);
|
|
83
61
|
} catch (err) {
|
|
84
|
-
console.error("
|
|
62
|
+
console.error("SoundCloud error:", err.message);
|
|
85
63
|
return [];
|
|
86
64
|
}
|
|
87
|
-
}
|
|
65
|
+
};
|
|
88
66
|
|
|
89
|
-
|
|
90
|
-
const timeStep =
|
|
91
|
-
const counter = Buffer.
|
|
92
|
-
counter.
|
|
67
|
+
const generateToken = () => {
|
|
68
|
+
const timeStep = (Date.now() / 30000) | 0;
|
|
69
|
+
const counter = Buffer.allocUnsafe(8);
|
|
70
|
+
counter.writeBigUInt64BE(BigInt(timeStep), 0);
|
|
93
71
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
const hash = hmac.digest();
|
|
97
|
-
const offset = hash[hash.length - 1] & 0x0f;
|
|
72
|
+
const hash = crypto.createHmac('sha1', TOTP_SECRET).update(counter).digest();
|
|
73
|
+
const offset = hash[19] & 0x0f;
|
|
98
74
|
|
|
99
75
|
const binCode = (
|
|
100
|
-
(hash[offset] << 24
|
|
101
|
-
|
|
102
|
-
|
|
76
|
+
(hash[offset] & 0x7f) << 24 |
|
|
77
|
+
hash[offset + 1] << 16 |
|
|
78
|
+
hash[offset + 2] << 8 |
|
|
103
79
|
hash[offset + 3]
|
|
104
|
-
)
|
|
80
|
+
);
|
|
105
81
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
82
|
+
return [
|
|
83
|
+
(binCode % 1000000).toString().padStart(6, '0'),
|
|
84
|
+
timeStep * 30000
|
|
85
|
+
];
|
|
86
|
+
};
|
|
109
87
|
|
|
110
|
-
async
|
|
88
|
+
const spotifyAutoPlay = async (seedTrackId) => {
|
|
111
89
|
const [totp, ts] = generateToken();
|
|
112
|
-
|
|
113
|
-
reason: "transport",
|
|
114
|
-
productType: "embed",
|
|
115
|
-
totp,
|
|
116
|
-
totpVer: "5",
|
|
117
|
-
ts: ts.toString()
|
|
118
|
-
});
|
|
119
|
-
|
|
90
|
+
|
|
120
91
|
try {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
92
|
+
const tokenUrl = `https://open.spotify.com/api/token?reason=init&productType=embed&totp=${totp}&totpVer=5&ts=${ts}`;
|
|
93
|
+
const tokenResponse = await fastFetch(tokenUrl);
|
|
94
|
+
const { accessToken } = JSON.parse(tokenResponse);
|
|
123
95
|
|
|
124
|
-
if (!accessToken) throw new Error("
|
|
96
|
+
if (!accessToken) throw new Error("No access token");
|
|
125
97
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
98
|
+
const recUrl = `https://api.spotify.com/v1/recommendations?limit=10&seed_tracks=${seedTrackId}`;
|
|
99
|
+
const recResponse = await fastFetch(recUrl, {
|
|
100
|
+
headers: { Authorization: `Bearer ${accessToken}` }
|
|
101
|
+
});
|
|
130
102
|
|
|
131
|
-
const { tracks } = JSON.parse(
|
|
132
|
-
if (!tracks?.length) throw new Error("No tracks
|
|
103
|
+
const { tracks } = JSON.parse(recResponse);
|
|
104
|
+
if (!tracks?.length) throw new Error("No tracks");
|
|
133
105
|
|
|
134
106
|
return tracks[Math.random() * tracks.length | 0].id;
|
|
135
107
|
} catch (err) {
|
|
136
|
-
console.error("Spotify
|
|
108
|
+
console.error("Spotify error:", err.message);
|
|
137
109
|
throw err;
|
|
138
110
|
}
|
|
139
|
-
}
|
|
111
|
+
};
|
|
140
112
|
|
|
141
113
|
module.exports = {
|
|
142
114
|
scAutoPlay: soundAutoPlay,
|
|
@@ -58,13 +58,12 @@ class Player extends EventEmitter {
|
|
|
58
58
|
this._updateTimeout = null;
|
|
59
59
|
this._dataStore = new Map();
|
|
60
60
|
|
|
61
|
-
this.on("playerUpdate", (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
61
|
+
this.on("playerUpdate", (packet) => {
|
|
62
|
+
this.position = packet.state.position;
|
|
63
|
+
this.connected = packet.state.connected;
|
|
64
|
+
this.ping = packet.state.ping;
|
|
65
|
+
|
|
66
|
+
this.aqua.emit("playerUpdate", this, packet);
|
|
68
67
|
});
|
|
69
68
|
|
|
70
69
|
this.on("event", async (payload) => {
|