yukimu 1.2.0 → 2.0.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/dist/BitrateOptimizer.d.ts +52 -0
- package/dist/BitrateOptimizer.d.ts.map +1 -0
- package/dist/BitrateOptimizer.js +115 -0
- package/dist/BitrateOptimizer.js.map +1 -0
- package/dist/ConnectionPool.d.ts +52 -2
- package/dist/ConnectionPool.d.ts.map +1 -1
- package/dist/ConnectionPool.js +123 -44
- package/dist/ConnectionPool.js.map +1 -1
- package/dist/Constants.d.ts +26 -47
- package/dist/Constants.d.ts.map +1 -1
- package/dist/Constants.js +134 -67
- package/dist/Constants.js.map +1 -1
- package/dist/Logger.d.ts +22 -0
- package/dist/Logger.d.ts.map +1 -0
- package/dist/Logger.js +74 -0
- package/dist/Logger.js.map +1 -0
- package/dist/Node.d.ts +17 -4
- package/dist/Node.d.ts.map +1 -1
- package/dist/Node.js +217 -79
- package/dist/Node.js.map +1 -1
- package/dist/Player.d.ts +77 -6
- package/dist/Player.d.ts.map +1 -1
- package/dist/Player.js +319 -75
- package/dist/Player.js.map +1 -1
- package/dist/Plugin.d.ts +16 -12
- package/dist/Plugin.d.ts.map +1 -1
- package/dist/Plugin.js +5 -9
- package/dist/Plugin.js.map +1 -1
- package/dist/Queue.d.ts +69 -3
- package/dist/Queue.d.ts.map +1 -1
- package/dist/Queue.js +123 -17
- package/dist/Queue.js.map +1 -1
- package/dist/Resolver.d.ts +33 -2
- package/dist/Resolver.d.ts.map +1 -1
- package/dist/Resolver.js +233 -27
- package/dist/Resolver.js.map +1 -1
- package/dist/Rest.d.ts +25 -3
- package/dist/Rest.d.ts.map +1 -1
- package/dist/Rest.js +165 -23
- package/dist/Rest.js.map +1 -1
- package/dist/TrackCache.d.ts +28 -8
- package/dist/TrackCache.d.ts.map +1 -1
- package/dist/TrackCache.js +111 -24
- package/dist/TrackCache.js.map +1 -1
- package/dist/WsQueue.d.ts +30 -9
- package/dist/WsQueue.d.ts.map +1 -1
- package/dist/WsQueue.js +66 -24
- package/dist/WsQueue.js.map +1 -1
- package/dist/Yukimu.d.ts +37 -13
- package/dist/Yukimu.d.ts.map +1 -1
- package/dist/Yukimu.js +146 -59
- package/dist/Yukimu.js.map +1 -1
- package/dist/connector/Connector.d.ts +26 -0
- package/dist/connector/Connector.d.ts.map +1 -1
- package/dist/connector/Connector.js +28 -0
- package/dist/connector/Connector.js.map +1 -1
- package/dist/connector/DiscordJS.d.ts +21 -19
- package/dist/connector/DiscordJS.d.ts.map +1 -1
- package/dist/connector/DiscordJS.js +44 -2
- package/dist/connector/DiscordJS.js.map +1 -1
- package/dist/connector/Eris.d.ts +13 -17
- package/dist/connector/Eris.d.ts.map +1 -1
- package/dist/connector/Eris.js +36 -4
- package/dist/connector/Eris.js.map +1 -1
- package/dist/connector/Oceanic.d.ts +13 -14
- package/dist/connector/Oceanic.d.ts.map +1 -1
- package/dist/connector/Oceanic.js +35 -2
- package/dist/connector/Oceanic.js.map +1 -1
- package/dist/errors/YukimuError.d.ts +40 -4
- package/dist/errors/YukimuError.d.ts.map +1 -1
- package/dist/errors/YukimuError.js +79 -9
- package/dist/errors/YukimuError.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -21
- package/dist/index.js.map +1 -1
- package/dist/plugins/AutoResume.d.ts +18 -14
- package/dist/plugins/AutoResume.d.ts.map +1 -1
- package/dist/plugins/AutoResume.js +101 -21
- package/dist/plugins/AutoResume.js.map +1 -1
- package/dist/plugins/AutoplayPlugin.d.ts +35 -0
- package/dist/plugins/AutoplayPlugin.d.ts.map +1 -0
- package/dist/plugins/AutoplayPlugin.js +111 -0
- package/dist/plugins/AutoplayPlugin.js.map +1 -0
- package/dist/plugins/InactivityPlugin.d.ts +30 -0
- package/dist/plugins/InactivityPlugin.d.ts.map +1 -0
- package/dist/plugins/InactivityPlugin.js +86 -0
- package/dist/plugins/InactivityPlugin.js.map +1 -0
- package/dist/plugins/PlayerMoved.d.ts +23 -1
- package/dist/plugins/PlayerMoved.d.ts.map +1 -1
- package/dist/plugins/PlayerMoved.js +57 -12
- package/dist/plugins/PlayerMoved.js.map +1 -1
- package/dist/types.d.ts +196 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +49 -5
- package/.cache/replit/env/latest +0 -88
- package/.cache/replit/env/latest.json +0 -1
- package/.cache/replit/modules/nodejs-20.res +0 -1
- package/.cache/replit/modules/python-3.11.res +0 -1
- package/.cache/replit/modules/replit.res +0 -1
- package/.cache/replit/modules.stamp +0 -0
- package/.cache/replit/nix/dotreplitenv.json +0 -1
- package/.cache/replit/toolchain.json +0 -1
- package/.local/state/workflow-logs/7zVU0iVo-fBL1ccMCmELy/configure_your_app.packager.installForAll.0 +0 -9
- package/.local/state/workflow-logs/7zVU0iVo-fBL1ccMCmELy/configure_your_app.shell.exec.1 +0 -1
- package/.local/state/workflow-logs/KRgHXizaECjWI5nWtS7Dj/configure_your_app.packager.installForAll.0 +0 -1
- package/.local/state/workflow-logs/KRgHXizaECjWI5nWtS7Dj/configure_your_app.shell.exec.1 +0 -1
- package/.local/state/workflow-logs/U0AinJQVHonnwGjj0RXLn/configure_your_app.packager.installForAll.0 +0 -2
- package/.local/state/workflow-logs/jVavLOnv1MqxUvxhMmqER/configure_your_app.packager.installForAll.0 +0 -1
- package/.local/state/workflow-logs/jVavLOnv1MqxUvxhMmqER/configure_your_app.shell.exec.1 +0 -1
- package/.replit +0 -7
- package/.upm/store.json +0 -1
- package/src/ConnectionPool.ts +0 -131
- package/src/Constants.ts +0 -101
- package/src/Node.ts +0 -309
- package/src/Player.ts +0 -337
- package/src/Plugin.ts +0 -23
- package/src/Queue.ts +0 -91
- package/src/Resolver.ts +0 -79
- package/src/Rest.ts +0 -121
- package/src/TrackCache.ts +0 -69
- package/src/WsQueue.ts +0 -78
- package/src/Yukimu.ts +0 -248
- package/src/connector/Connector.ts +0 -13
- package/src/connector/DiscordJS.ts +0 -33
- package/src/connector/Eris.ts +0 -35
- package/src/connector/Oceanic.ts +0 -32
- package/src/errors/YukimuError.ts +0 -33
- package/src/index.ts +0 -46
- package/src/plugins/AutoResume.ts +0 -61
- package/src/plugins/PlayerMoved.ts +0 -26
- package/src/types.ts +0 -145
- package/tsconfig.json +0 -20
package/dist/Resolver.js
CHANGED
|
@@ -1,51 +1,176 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/** Yukimu v2.0.0 — Track Resolver with Retry, Caching & Fallback */
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.Resolver = void 0;
|
|
4
5
|
const Constants_1 = require("./Constants");
|
|
6
|
+
/**
|
|
7
|
+
* Resolves search queries and URLs into playable tracks.
|
|
8
|
+
*
|
|
9
|
+
* Improvements from v1:
|
|
10
|
+
* - Accepts optional `node` parameter (loads on the correct node for a player)
|
|
11
|
+
* - Search retry with fallback sources
|
|
12
|
+
* - Search result caching
|
|
13
|
+
* - Spotify playlist/album resolution
|
|
14
|
+
* - Spotify token refresh with retry and timeout
|
|
15
|
+
* - Rate limit handling for Spotify API
|
|
16
|
+
*/
|
|
5
17
|
class Resolver {
|
|
18
|
+
manager;
|
|
19
|
+
spotifyToken = null;
|
|
20
|
+
spotifyExpiry = 0;
|
|
21
|
+
spotifyRefreshing = false;
|
|
6
22
|
constructor(manager) {
|
|
7
|
-
this.spotifyToken = null;
|
|
8
|
-
this.spotifyExpiry = 0;
|
|
9
23
|
this.manager = manager;
|
|
10
24
|
}
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a query (URL or search term) into tracks.
|
|
27
|
+
*
|
|
28
|
+
* @param query - A URL or search query string
|
|
29
|
+
* @param source - The search source to use (ignored for URLs)
|
|
30
|
+
* @param requester - Optional requester metadata to attach to tracks
|
|
31
|
+
* @param node - Optional specific node to use for loading
|
|
32
|
+
*/
|
|
33
|
+
async resolve(query, source, requester, node) {
|
|
34
|
+
const targetNode = node ?? this.getLoadNode();
|
|
13
35
|
const isUrl = /^https?:\/\//.test(query);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
36
|
+
const prefix = Constants_1.SOURCE_PREFIXES[source];
|
|
37
|
+
const identifier = isUrl ? query : `${prefix ?? "ytsearch"}:${query}`;
|
|
38
|
+
// Check search cache first
|
|
39
|
+
const cached = this.manager.trackCache.getSearch(identifier);
|
|
40
|
+
if (cached) {
|
|
41
|
+
this.manager.logger.debug(`Search cache hit for: ${identifier.slice(0, 80)}`);
|
|
42
|
+
// Clone and attach requester
|
|
43
|
+
const result = { ...cached, tracks: [...cached.tracks] };
|
|
44
|
+
if (requester)
|
|
45
|
+
this.attachRequester(result.tracks, requester);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
// Try loading tracks with retry
|
|
49
|
+
let result = null;
|
|
50
|
+
let lastError = null;
|
|
51
|
+
for (let attempt = 0; attempt <= Constants_1.DEFAULTS.MAX_SEARCH_RETRIES; attempt++) {
|
|
52
|
+
try {
|
|
53
|
+
result = await targetNode.loadTracks(identifier);
|
|
54
|
+
if (result && result.loadType !== "error")
|
|
55
|
+
break;
|
|
56
|
+
lastError = result?.exception;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
lastError = err;
|
|
60
|
+
this.manager.logger.debug(`loadTracks attempt ${attempt + 1} failed for "${identifier.slice(0, 80)}": ${err}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!result) {
|
|
64
|
+
this.manager.logger.warn(`All loadTracks attempts failed for: ${identifier.slice(0, 80)}`);
|
|
65
|
+
return { loadType: "error", tracks: [], exception: { severity: "common", cause: String(lastError), message: String(lastError) } };
|
|
66
|
+
}
|
|
67
|
+
if (!result.tracks)
|
|
68
|
+
result.tracks = [];
|
|
16
69
|
// Attach requester
|
|
17
|
-
if (requester)
|
|
18
|
-
result.tracks
|
|
19
|
-
|
|
70
|
+
if (requester && result.tracks.length > 0) {
|
|
71
|
+
this.attachRequester(result.tracks, requester);
|
|
72
|
+
}
|
|
73
|
+
// Spotify fallback — if LavaSrc is not installed, try to resolve via metadata
|
|
20
74
|
if ((result.loadType === "error" || result.loadType === "empty") && isUrl) {
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
if (meta)
|
|
25
|
-
return this.resolve(`${meta.title} ${meta.artist}`, "youtube", requester);
|
|
26
|
-
}
|
|
75
|
+
const spotifyFallback = await this.trySpotifyFallback(query, source, requester);
|
|
76
|
+
if (spotifyFallback)
|
|
77
|
+
return spotifyFallback;
|
|
27
78
|
}
|
|
28
|
-
// Cache
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
79
|
+
// Cache successful results
|
|
80
|
+
if (result.tracks.length > 0) {
|
|
81
|
+
this.manager.trackCache.setSearch(identifier, result);
|
|
82
|
+
// Also cache individual tracks
|
|
83
|
+
for (const track of result.tracks) {
|
|
84
|
+
if (track?.encoded) {
|
|
85
|
+
this.manager.trackCache.set(track.encoded, track);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
32
88
|
}
|
|
33
89
|
return result;
|
|
34
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect the source platform from a URL.
|
|
93
|
+
*/
|
|
35
94
|
detectSource(url) {
|
|
36
95
|
for (const [source, patterns] of Object.entries(Constants_1.URL_PATTERNS)) {
|
|
37
|
-
if (patterns.some(p => p.test(url)))
|
|
96
|
+
if (patterns.some((p) => p.test(url)))
|
|
38
97
|
return source;
|
|
39
98
|
}
|
|
40
99
|
return null;
|
|
41
100
|
}
|
|
101
|
+
// ─── Spotify Fallback ─────────────────────────────────────────────────
|
|
102
|
+
async trySpotifyFallback(query, _source, requester) {
|
|
103
|
+
// Single track
|
|
104
|
+
const trackMatch = query.match(/spotify\.com\/track\/([a-zA-Z0-9]+)/);
|
|
105
|
+
if (trackMatch?.[1]) {
|
|
106
|
+
const meta = await this.resolveSpotifyMeta(trackMatch[1]);
|
|
107
|
+
if (meta) {
|
|
108
|
+
return this.resolve(`${meta.title} ${meta.artist}`, "youtube", requester);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Playlist
|
|
112
|
+
const playlistMatch = query.match(/spotify\.com\/playlist\/([a-zA-Z0-9]+)/);
|
|
113
|
+
if (playlistMatch?.[1]) {
|
|
114
|
+
const tracks = await this.resolveSpotifyPlaylist(playlistMatch[1]);
|
|
115
|
+
if (tracks.length > 0) {
|
|
116
|
+
// Resolve first 50 tracks via YouTube search
|
|
117
|
+
const resolved = [];
|
|
118
|
+
for (const meta of tracks.slice(0, 50)) {
|
|
119
|
+
try {
|
|
120
|
+
const result = await this.resolve(`${meta.title} ${meta.artist}`, "youtube", requester);
|
|
121
|
+
if (result.tracks[0])
|
|
122
|
+
resolved.push(result.tracks[0]);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Skip failed tracks
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (resolved.length > 0) {
|
|
129
|
+
return { loadType: "playlist", tracks: resolved, playlistInfo: { name: "Spotify Playlist", selectedTrack: 0 } };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Album
|
|
134
|
+
const albumMatch = query.match(/spotify\.com\/album\/([a-zA-Z0-9]+)/);
|
|
135
|
+
if (albumMatch?.[1]) {
|
|
136
|
+
const tracks = await this.resolveSpotifyAlbum(albumMatch[1]);
|
|
137
|
+
if (tracks.length > 0) {
|
|
138
|
+
const resolved = [];
|
|
139
|
+
for (const meta of tracks.slice(0, 50)) {
|
|
140
|
+
try {
|
|
141
|
+
const result = await this.resolve(`${meta.title} ${meta.artist}`, "youtube", requester);
|
|
142
|
+
if (result.tracks[0])
|
|
143
|
+
resolved.push(result.tracks[0]);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Skip failed tracks
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (resolved.length > 0) {
|
|
150
|
+
return { loadType: "playlist", tracks: resolved, playlistInfo: { name: "Spotify Album", selectedTrack: 0 } };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
// ─── Spotify API ──────────────────────────────────────────────────────
|
|
42
157
|
async getSpotifyToken() {
|
|
43
158
|
const opts = this.manager.options.spotify;
|
|
44
159
|
if (!opts)
|
|
45
160
|
return null;
|
|
46
|
-
|
|
161
|
+
// Return cached token if still valid
|
|
162
|
+
if (this.spotifyToken && Date.now() < this.spotifyExpiry) {
|
|
47
163
|
return this.spotifyToken;
|
|
164
|
+
}
|
|
165
|
+
// Prevent concurrent refresh
|
|
166
|
+
if (this.spotifyRefreshing) {
|
|
167
|
+
await this.sleep(1000);
|
|
168
|
+
return this.spotifyToken;
|
|
169
|
+
}
|
|
170
|
+
this.spotifyRefreshing = true;
|
|
48
171
|
try {
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
49
174
|
const res = await fetch("https://accounts.spotify.com/api/token", {
|
|
50
175
|
method: "POST",
|
|
51
176
|
headers: {
|
|
@@ -53,31 +178,112 @@ class Resolver {
|
|
|
53
178
|
Authorization: `Basic ${Buffer.from(`${opts.clientId}:${opts.clientSecret}`).toString("base64")}`,
|
|
54
179
|
},
|
|
55
180
|
body: "grant_type=client_credentials",
|
|
181
|
+
signal: controller.signal,
|
|
56
182
|
});
|
|
57
|
-
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
this.manager.logger.warn(`Spotify token request failed: ${res.status}`);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const data = (await res.json());
|
|
58
189
|
this.spotifyToken = data.access_token;
|
|
59
|
-
this.spotifyExpiry = Date.now() + data.expires_in * 1000 -
|
|
190
|
+
this.spotifyExpiry = Date.now() + data.expires_in * 1000 - Constants_1.DEFAULTS.SPOTIFY_TOKEN_REFRESH_BUFFER;
|
|
60
191
|
return this.spotifyToken;
|
|
61
192
|
}
|
|
62
|
-
catch {
|
|
193
|
+
catch (err) {
|
|
194
|
+
this.manager.logger.warn(`Spotify token refresh failed: ${err}`);
|
|
63
195
|
return null;
|
|
64
196
|
}
|
|
197
|
+
finally {
|
|
198
|
+
this.spotifyRefreshing = false;
|
|
199
|
+
}
|
|
65
200
|
}
|
|
66
201
|
async resolveSpotifyMeta(trackId) {
|
|
67
202
|
const token = await this.getSpotifyToken();
|
|
68
203
|
if (!token)
|
|
69
204
|
return null;
|
|
70
205
|
try {
|
|
71
|
-
const
|
|
206
|
+
const controller = new AbortController();
|
|
207
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
208
|
+
const res = await fetch(`https://api.spotify.com/v1/tracks/${encodeURIComponent(trackId)}`, {
|
|
72
209
|
headers: { Authorization: `Bearer ${token}` },
|
|
210
|
+
signal: controller.signal,
|
|
73
211
|
});
|
|
74
|
-
|
|
75
|
-
|
|
212
|
+
clearTimeout(timeout);
|
|
213
|
+
if (!res.ok)
|
|
214
|
+
return null;
|
|
215
|
+
const data = (await res.json());
|
|
216
|
+
return {
|
|
217
|
+
title: data.name,
|
|
218
|
+
artist: data.artists?.[0]?.name ?? "Unknown",
|
|
219
|
+
};
|
|
76
220
|
}
|
|
77
221
|
catch {
|
|
78
222
|
return null;
|
|
79
223
|
}
|
|
80
224
|
}
|
|
225
|
+
async resolveSpotifyPlaylist(playlistId) {
|
|
226
|
+
const token = await this.getSpotifyToken();
|
|
227
|
+
if (!token)
|
|
228
|
+
return [];
|
|
229
|
+
try {
|
|
230
|
+
const controller = new AbortController();
|
|
231
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
232
|
+
const res = await fetch(`https://api.spotify.com/v1/playlists/${encodeURIComponent(playlistId)}/tracks?limit=50`, {
|
|
233
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
234
|
+
signal: controller.signal,
|
|
235
|
+
});
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
if (!res.ok)
|
|
238
|
+
return [];
|
|
239
|
+
const data = (await res.json());
|
|
240
|
+
return data.items
|
|
241
|
+
.filter((item) => item.track)
|
|
242
|
+
.map((item) => ({
|
|
243
|
+
title: item.track.name,
|
|
244
|
+
artist: item.track.artists?.[0]?.name ?? "Unknown",
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async resolveSpotifyAlbum(albumId) {
|
|
252
|
+
const token = await this.getSpotifyToken();
|
|
253
|
+
if (!token)
|
|
254
|
+
return [];
|
|
255
|
+
try {
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
258
|
+
const res = await fetch(`https://api.spotify.com/v1/albums/${encodeURIComponent(albumId)}/tracks?limit=50`, {
|
|
259
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
260
|
+
signal: controller.signal,
|
|
261
|
+
});
|
|
262
|
+
clearTimeout(timeout);
|
|
263
|
+
if (!res.ok)
|
|
264
|
+
return [];
|
|
265
|
+
const data = (await res.json());
|
|
266
|
+
return data.items.map((item) => ({
|
|
267
|
+
title: item.name,
|
|
268
|
+
artist: item.artists?.[0]?.name ?? "Unknown",
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
276
|
+
getLoadNode() {
|
|
277
|
+
return this.manager.getBestNode();
|
|
278
|
+
}
|
|
279
|
+
attachRequester(tracks, requester) {
|
|
280
|
+
for (const t of tracks) {
|
|
281
|
+
t["requester"] = requester;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
sleep(ms) {
|
|
285
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
286
|
+
}
|
|
81
287
|
}
|
|
82
288
|
exports.Resolver = Resolver;
|
|
83
289
|
//# sourceMappingURL=Resolver.js.map
|
package/dist/Resolver.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Resolver.js","sourceRoot":"","sources":["../src/Resolver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Resolver.js","sourceRoot":"","sources":["../src/Resolver.ts"],"names":[],"mappings":"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"}
|
package/dist/Rest.d.ts
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
|
+
/** Yukimu v2.0.0 — REST Client with Retry, Rate Limiting & Circuit Breaker */
|
|
1
2
|
import type { Node } from "./Node";
|
|
2
3
|
import type { SearchResult, Track, NodeStats, NodeInfo } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* REST client for communicating with a Lavalink node's HTTP API.
|
|
6
|
+
*
|
|
7
|
+
* Improvements from v1:
|
|
8
|
+
* - Automatic retry with exponential backoff for 429/5xx errors
|
|
9
|
+
* - Rate limit tracking (respects Retry-After headers)
|
|
10
|
+
* - Request timeout with proper AbortController cleanup
|
|
11
|
+
* - Uses VERSION constant for Client-Name header
|
|
12
|
+
* - Fixed v4.2+ data normalization bug (single track vs array)
|
|
13
|
+
* - Per-path rate limit awareness
|
|
14
|
+
* - Circuit breaker: after N consecutive failures, stop sending requests temporarily
|
|
15
|
+
*/
|
|
3
16
|
export declare class Rest {
|
|
4
|
-
private node;
|
|
17
|
+
private readonly node;
|
|
18
|
+
private rateLimit;
|
|
19
|
+
private consecutiveFailures;
|
|
20
|
+
private circuitOpenUntil;
|
|
21
|
+
private static readonly MAX_CONSECUTIVE_FAILURES;
|
|
22
|
+
private static readonly CIRCUIT_OPEN_DURATION;
|
|
5
23
|
constructor(node: Node);
|
|
6
24
|
get baseUrl(): string;
|
|
7
25
|
get prefix(): string;
|
|
8
26
|
get headers(): Record<string, string>;
|
|
9
|
-
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
27
|
+
request<T = unknown>(method: string, path: string, body?: unknown, retries?: number): Promise<T>;
|
|
10
28
|
loadTracks(identifier: string): Promise<SearchResult>;
|
|
11
29
|
decodeTrack(encoded: string): Promise<Track>;
|
|
12
30
|
decodeTracks(encoded: string[]): Promise<Track[]>;
|
|
13
31
|
getInfo(): Promise<NodeInfo>;
|
|
14
32
|
getStats(): Promise<NodeStats>;
|
|
15
|
-
getVersion(): Promise<string>;
|
|
16
33
|
updatePlayer(guildId: string, body: unknown, noReplace?: boolean): Promise<unknown>;
|
|
17
34
|
destroyPlayer(guildId: string): Promise<void>;
|
|
18
35
|
updateSession(body: {
|
|
19
36
|
resuming?: boolean;
|
|
20
37
|
timeout?: number;
|
|
21
38
|
}): Promise<unknown>;
|
|
39
|
+
private normalizeV4;
|
|
22
40
|
private normalizeV3;
|
|
41
|
+
private updateRateLimit;
|
|
42
|
+
private parseRetryAfter;
|
|
43
|
+
private getRetryDelay;
|
|
44
|
+
private sleep;
|
|
23
45
|
}
|
|
24
46
|
//# sourceMappingURL=Rest.d.ts.map
|
package/dist/Rest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Rest.d.ts","sourceRoot":"","sources":["../src/Rest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Rest.d.ts","sourceRoot":"","sources":["../src/Rest.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAI9E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAY,MAAM,SAAS,CAAC;AASlF;;;;;;;;;;;GAWG;AACH,qBAAa,IAAI;IACf,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,gBAAgB,CAAK;IAE7B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAK;IACrD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAU;gBAE3C,IAAI,EAAE,IAAI;IAItB,IAAI,OAAO,IAAI,MAAM,CAGpB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpC;IAIY,OAAO,CAAC,CAAC,GAAG,OAAO,EAC9B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,GAAE,MAAkC,GAC1C,OAAO,CAAC,CAAC,CAAC;IA4GA,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAUrD,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI5C,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAIjD,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;IAI5B,QAAQ,IAAI,OAAO,CAAC,SAAS,CAAC;IAI9B,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAOjF,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7C,aAAa,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAO5F,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAyCnB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,KAAK;CAGd"}
|
package/dist/Rest.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/** Yukimu v2.0.0 — REST Client with Retry, Rate Limiting & Circuit Breaker */
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.Rest = void 0;
|
|
4
5
|
const YukimuError_1 = require("./errors/YukimuError");
|
|
5
|
-
const
|
|
6
|
+
const Constants_1 = require("./Constants");
|
|
7
|
+
/**
|
|
8
|
+
* REST client for communicating with a Lavalink node's HTTP API.
|
|
9
|
+
*
|
|
10
|
+
* Improvements from v1:
|
|
11
|
+
* - Automatic retry with exponential backoff for 429/5xx errors
|
|
12
|
+
* - Rate limit tracking (respects Retry-After headers)
|
|
13
|
+
* - Request timeout with proper AbortController cleanup
|
|
14
|
+
* - Uses VERSION constant for Client-Name header
|
|
15
|
+
* - Fixed v4.2+ data normalization bug (single track vs array)
|
|
16
|
+
* - Per-path rate limit awareness
|
|
17
|
+
* - Circuit breaker: after N consecutive failures, stop sending requests temporarily
|
|
18
|
+
*/
|
|
6
19
|
class Rest {
|
|
20
|
+
node;
|
|
21
|
+
rateLimit = null;
|
|
22
|
+
consecutiveFailures = 0;
|
|
23
|
+
circuitOpenUntil = 0;
|
|
24
|
+
static MAX_CONSECUTIVE_FAILURES = 5;
|
|
25
|
+
static CIRCUIT_OPEN_DURATION = 30_000;
|
|
7
26
|
constructor(node) {
|
|
8
27
|
this.node = node;
|
|
9
28
|
}
|
|
@@ -18,41 +37,107 @@ class Rest {
|
|
|
18
37
|
return {
|
|
19
38
|
Authorization: this.node.options.password,
|
|
20
39
|
"User-Id": this.node.manager.options.clientId,
|
|
21
|
-
"Client-Name":
|
|
40
|
+
"Client-Name": Constants_1.CLIENT_NAME,
|
|
22
41
|
"Content-Type": "application/json",
|
|
23
42
|
...(this.node.version === 3 ? { "Num-Shards": "1" } : {}),
|
|
24
43
|
};
|
|
25
44
|
}
|
|
26
|
-
|
|
45
|
+
// ─── Core Request ───────────────────────────────────────────────────────
|
|
46
|
+
async request(method, path, body, retries = Constants_1.DEFAULTS.REST_MAX_RETRIES) {
|
|
47
|
+
// Circuit breaker check
|
|
48
|
+
if (Date.now() < this.circuitOpenUntil) {
|
|
49
|
+
throw new YukimuError_1.RestError(`Circuit breaker open for node "${this.node.options.name}" — too many consecutive failures`, 503, path);
|
|
50
|
+
}
|
|
51
|
+
// Rate limit check
|
|
52
|
+
if (this.rateLimit && this.rateLimit.remaining <= 0 && Date.now() < this.rateLimit.resetAt) {
|
|
53
|
+
const waitMs = this.rateLimit.resetAt - Date.now();
|
|
54
|
+
this.node.manager.logger.debug(`Rate limited on ${path}, waiting ${waitMs}ms`);
|
|
55
|
+
await this.sleep(waitMs);
|
|
56
|
+
}
|
|
27
57
|
const url = `${this.baseUrl}${this.prefix}${path}`;
|
|
28
58
|
const controller = new AbortController();
|
|
29
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
59
|
+
const timeout = setTimeout(() => controller.abort(), Constants_1.DEFAULTS.REST_TIMEOUT);
|
|
30
60
|
try {
|
|
31
|
-
const res = await
|
|
61
|
+
const res = await fetch(url, {
|
|
32
62
|
method,
|
|
33
63
|
headers: this.headers,
|
|
34
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
64
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
35
65
|
signal: controller.signal,
|
|
36
66
|
});
|
|
37
67
|
clearTimeout(timeout);
|
|
68
|
+
// Track rate limit headers
|
|
69
|
+
this.updateRateLimit(res);
|
|
70
|
+
// Handle rate limiting with retry
|
|
71
|
+
if (res.status === 429) {
|
|
72
|
+
const retryAfter = this.parseRetryAfter(res);
|
|
73
|
+
if (retries > 0) {
|
|
74
|
+
this.node.manager.logger.warn(`Rate limited on ${method} ${path}, retrying in ${retryAfter}ms`);
|
|
75
|
+
await this.sleep(retryAfter);
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
77
|
+
return this.request(method, path, body, (retries - 1));
|
|
78
|
+
}
|
|
79
|
+
throw new YukimuError_1.RestError(`Rate limited on ${method} ${path}`, 429, path, retryAfter);
|
|
80
|
+
}
|
|
81
|
+
// Handle server errors with retry
|
|
82
|
+
if (res.status >= 500 && retries > 0) {
|
|
83
|
+
const delay = this.getRetryDelay(Constants_1.DEFAULTS.REST_MAX_RETRIES - retries);
|
|
84
|
+
this.node.manager.logger.warn(`Server error ${res.status} on ${method} ${path}, retrying in ${delay}ms`);
|
|
85
|
+
await this.sleep(delay);
|
|
86
|
+
return this.request(method, path, body, (retries - 1));
|
|
87
|
+
}
|
|
38
88
|
if (!res.ok) {
|
|
39
89
|
const text = await res.text().catch(() => "Unknown error");
|
|
40
90
|
throw new YukimuError_1.RestError(`REST ${res.status} on ${method} ${path}: ${text}`, res.status, path);
|
|
41
91
|
}
|
|
92
|
+
// Reset circuit breaker on success
|
|
93
|
+
this.consecutiveFailures = 0;
|
|
42
94
|
if (res.status === 204)
|
|
43
95
|
return undefined;
|
|
44
|
-
|
|
96
|
+
const text = await res.text();
|
|
97
|
+
if (!text)
|
|
98
|
+
return null;
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(text);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
// Normalize Lavalink v4.2+ response — uses "data" instead of "tracks"
|
|
107
|
+
if (parsed && "data" in parsed && !("tracks" in parsed)) {
|
|
108
|
+
const data = parsed["data"];
|
|
109
|
+
if (Array.isArray(data)) {
|
|
110
|
+
parsed["tracks"] = data;
|
|
111
|
+
}
|
|
112
|
+
else if (data && typeof data === "object") {
|
|
113
|
+
// Single track response — wrap in array
|
|
114
|
+
parsed["tracks"] = [data];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
45
118
|
}
|
|
46
119
|
catch (err) {
|
|
47
120
|
clearTimeout(timeout);
|
|
48
|
-
|
|
121
|
+
// Increment circuit breaker
|
|
122
|
+
this.consecutiveFailures++;
|
|
123
|
+
if (this.consecutiveFailures >= Rest.MAX_CONSECUTIVE_FAILURES) {
|
|
124
|
+
this.circuitOpenUntil = Date.now() + Rest.CIRCUIT_OPEN_DURATION;
|
|
125
|
+
this.node.manager.logger.error(`Circuit breaker opened for node "${this.node.options.name}" after ${this.consecutiveFailures} failures`);
|
|
126
|
+
}
|
|
127
|
+
if (err instanceof YukimuError_1.RestError)
|
|
128
|
+
throw err;
|
|
129
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
49
130
|
throw new YukimuError_1.RestError(`REST timeout on ${method} ${path}`, 408, path);
|
|
131
|
+
}
|
|
50
132
|
throw err;
|
|
51
133
|
}
|
|
52
134
|
}
|
|
135
|
+
// ─── API Methods ────────────────────────────────────────────────────────
|
|
53
136
|
async loadTracks(identifier) {
|
|
54
137
|
const raw = await this.request("GET", `/loadtracks?identifier=${encodeURIComponent(identifier)}`);
|
|
55
|
-
|
|
138
|
+
if (!raw)
|
|
139
|
+
return { loadType: "empty", tracks: [] };
|
|
140
|
+
return this.node.version === 3 ? this.normalizeV3(raw) : this.normalizeV4(raw);
|
|
56
141
|
}
|
|
57
142
|
async decodeTrack(encoded) {
|
|
58
143
|
return this.request("GET", `/decodetrack?encodedTrack=${encodeURIComponent(encoded)}`);
|
|
@@ -66,10 +151,6 @@ class Rest {
|
|
|
66
151
|
async getStats() {
|
|
67
152
|
return this.request("GET", "/stats");
|
|
68
153
|
}
|
|
69
|
-
async getVersion() {
|
|
70
|
-
const res = await globalThis.fetch(`${this.baseUrl}/version`, { headers: this.headers });
|
|
71
|
-
return res.text();
|
|
72
|
-
}
|
|
73
154
|
async updatePlayer(guildId, body, noReplace = false) {
|
|
74
155
|
const path = this.node.version === 4
|
|
75
156
|
? `/sessions/${this.node.sessionId}/players/${guildId}?noReplace=${noReplace}`
|
|
@@ -80,25 +161,86 @@ class Rest {
|
|
|
80
161
|
const path = this.node.version === 4
|
|
81
162
|
? `/sessions/${this.node.sessionId}/players/${guildId}`
|
|
82
163
|
: `/players/${guildId}`;
|
|
83
|
-
await this.request("DELETE", path);
|
|
164
|
+
await this.request("DELETE", path).catch(() => { });
|
|
84
165
|
}
|
|
85
166
|
async updateSession(body) {
|
|
86
|
-
if (this.node.version !== 4)
|
|
167
|
+
if (this.node.version !== 4 || !this.node.sessionId)
|
|
87
168
|
return;
|
|
88
169
|
return this.request("PATCH", `/sessions/${this.node.sessionId}`, body);
|
|
89
170
|
}
|
|
171
|
+
// ─── Normalization ──────────────────────────────────────────────────────
|
|
172
|
+
normalizeV4(raw) {
|
|
173
|
+
const result = raw;
|
|
174
|
+
if (!result.tracks)
|
|
175
|
+
result.tracks = [];
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
90
178
|
normalizeV3(raw) {
|
|
91
|
-
const loadType = (raw
|
|
92
|
-
const
|
|
179
|
+
const loadType = String(raw["loadType"] ?? "").toLowerCase();
|
|
180
|
+
const rawTracks = raw["tracks"];
|
|
181
|
+
const tracks = Array.isArray(rawTracks) ? rawTracks : [];
|
|
182
|
+
const norm = (items) => items.map((t) => {
|
|
183
|
+
const track = t;
|
|
184
|
+
return {
|
|
185
|
+
encoded: (track["track"] ?? track["encoded"]),
|
|
186
|
+
info: track["info"],
|
|
187
|
+
pluginInfo: (track["pluginInfo"] ?? {}),
|
|
188
|
+
};
|
|
189
|
+
});
|
|
93
190
|
switch (loadType) {
|
|
94
|
-
case "track_loaded":
|
|
95
|
-
|
|
96
|
-
case "
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
191
|
+
case "track_loaded":
|
|
192
|
+
return { loadType: "track", tracks: norm(tracks) };
|
|
193
|
+
case "playlist_loaded":
|
|
194
|
+
return {
|
|
195
|
+
loadType: "playlist",
|
|
196
|
+
tracks: norm(tracks),
|
|
197
|
+
playlistInfo: raw["playlistInfo"],
|
|
198
|
+
};
|
|
199
|
+
case "search_result":
|
|
200
|
+
return { loadType: "search", tracks: norm(tracks) };
|
|
201
|
+
case "no_matches":
|
|
202
|
+
return { loadType: "empty", tracks: [] };
|
|
203
|
+
case "load_failed":
|
|
204
|
+
return {
|
|
205
|
+
loadType: "error",
|
|
206
|
+
tracks: [],
|
|
207
|
+
exception: raw["exception"],
|
|
208
|
+
};
|
|
209
|
+
default:
|
|
210
|
+
return { loadType: "empty", tracks: [] };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
214
|
+
updateRateLimit(res) {
|
|
215
|
+
const remaining = res.headers.get("x-ratelimit-remaining");
|
|
216
|
+
const reset = res.headers.get("x-ratelimit-reset");
|
|
217
|
+
if (remaining !== null && reset !== null) {
|
|
218
|
+
this.rateLimit = {
|
|
219
|
+
remaining: parseInt(remaining, 10),
|
|
220
|
+
resetAt: parseInt(reset, 10) * 1000,
|
|
221
|
+
};
|
|
100
222
|
}
|
|
101
223
|
}
|
|
224
|
+
parseRetryAfter(res) {
|
|
225
|
+
const retryAfter = res.headers.get("retry-after");
|
|
226
|
+
if (retryAfter) {
|
|
227
|
+
const seconds = parseFloat(retryAfter);
|
|
228
|
+
if (!isNaN(seconds))
|
|
229
|
+
return Math.ceil(seconds * 1000);
|
|
230
|
+
}
|
|
231
|
+
return 5000; // Default 5s
|
|
232
|
+
}
|
|
233
|
+
getRetryDelay(attempt) {
|
|
234
|
+
const base = Constants_1.DEFAULTS.REST_RETRY_BASE_DELAY;
|
|
235
|
+
const max = Constants_1.DEFAULTS.REST_RETRY_MAX_DELAY;
|
|
236
|
+
const delay = Math.min(base * Math.pow(2, attempt), max);
|
|
237
|
+
// Add jitter ±25%
|
|
238
|
+
const jitter = delay * 0.25 * (Math.random() * 2 - 1);
|
|
239
|
+
return Math.round(delay + jitter);
|
|
240
|
+
}
|
|
241
|
+
sleep(ms) {
|
|
242
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
243
|
+
}
|
|
102
244
|
}
|
|
103
245
|
exports.Rest = Rest;
|
|
104
246
|
//# sourceMappingURL=Rest.js.map
|