soundcloud-api-ts 1.11.3 → 1.12.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/AGENTS.md +6 -1
- package/README.md +90 -1
- package/dist/{chunk-2747SK6H.js → chunk-D7AF372V.js} +236 -8
- package/dist/chunk-D7AF372V.js.map +1 -0
- package/dist/{chunk-HCEUCJMA.mjs → chunk-JLRQJWU5.mjs} +234 -9
- package/dist/chunk-JLRQJWU5.mjs.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +163 -1
- package/dist/index.d.ts +163 -1
- package/dist/index.js +75 -63
- package/dist/index.mjs +1 -1
- package/llms.txt +45 -0
- package/package.json +4 -2
- package/dist/chunk-2747SK6H.js.map +0 -1
- package/dist/chunk-HCEUCJMA.mjs.map +0 -1
|
@@ -15,7 +15,7 @@ function getRetryDelay(response, attempt, config) {
|
|
|
15
15
|
const retryAfter = response.headers.get("retry-after");
|
|
16
16
|
if (retryAfter) {
|
|
17
17
|
const seconds = Number(retryAfter);
|
|
18
|
-
if (!Number.isNaN(seconds)) return seconds * 1e3;
|
|
18
|
+
if (!Number.isNaN(seconds)) return Math.min(seconds * 1e3, 6e4);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
const base = config.retryBaseDelay * Math.pow(2, attempt);
|
|
@@ -91,9 +91,26 @@ async function scFetch(options, refreshCtx, onRequest) {
|
|
|
91
91
|
return void 0;
|
|
92
92
|
}
|
|
93
93
|
if (response.ok) {
|
|
94
|
-
const
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
if (typeof data === "object" && data !== null) {
|
|
96
|
+
const metaHeaders = {};
|
|
97
|
+
if (typeof response.headers.forEach === "function") {
|
|
98
|
+
response.headers.forEach((value, key) => {
|
|
99
|
+
metaHeaders[key] = value;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
Object.defineProperty(data, "_meta", {
|
|
104
|
+
value: { status: response.status, headers: metaHeaders },
|
|
105
|
+
enumerable: false,
|
|
106
|
+
configurable: true,
|
|
107
|
+
writable: true
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
}
|
|
95
112
|
emitTelemetry();
|
|
96
|
-
return
|
|
113
|
+
return data;
|
|
97
114
|
}
|
|
98
115
|
if (!isRetryable(response.status)) {
|
|
99
116
|
const body2 = await parseErrorBody(response);
|
|
@@ -108,6 +125,13 @@ async function scFetch(options, refreshCtx, onRequest) {
|
|
|
108
125
|
retryConfig.onDebug?.(
|
|
109
126
|
`Retry ${attempt + 1}/${retryConfig.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
110
127
|
);
|
|
128
|
+
retryConfig.onRetry?.({
|
|
129
|
+
attempt: retryCount,
|
|
130
|
+
delayMs,
|
|
131
|
+
reason: `${response.status} ${response.statusText}`,
|
|
132
|
+
status: response.status,
|
|
133
|
+
url
|
|
134
|
+
});
|
|
111
135
|
await delay(delayMs);
|
|
112
136
|
}
|
|
113
137
|
}
|
|
@@ -161,9 +185,26 @@ async function scFetchUrl(url, token, retryConfig, onRequest) {
|
|
|
161
185
|
return void 0;
|
|
162
186
|
}
|
|
163
187
|
if (response.ok) {
|
|
164
|
-
const
|
|
188
|
+
const data = await response.json();
|
|
189
|
+
if (typeof data === "object" && data !== null) {
|
|
190
|
+
const metaHeaders = {};
|
|
191
|
+
if (typeof response.headers.forEach === "function") {
|
|
192
|
+
response.headers.forEach((value, key) => {
|
|
193
|
+
metaHeaders[key] = value;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
Object.defineProperty(data, "_meta", {
|
|
198
|
+
value: { status: response.status, headers: metaHeaders },
|
|
199
|
+
enumerable: false,
|
|
200
|
+
configurable: true,
|
|
201
|
+
writable: true
|
|
202
|
+
});
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
165
206
|
emitTelemetry();
|
|
166
|
-
return
|
|
207
|
+
return data;
|
|
167
208
|
}
|
|
168
209
|
if (!isRetryable(response.status)) {
|
|
169
210
|
const body2 = await parseErrorBody(response);
|
|
@@ -178,6 +219,13 @@ async function scFetchUrl(url, token, retryConfig, onRequest) {
|
|
|
178
219
|
config.onDebug?.(
|
|
179
220
|
`Retry ${attempt + 1}/${config.maxRetries} after ${Math.round(delayMs)}ms (status ${response.status})`
|
|
180
221
|
);
|
|
222
|
+
config.onRetry?.({
|
|
223
|
+
attempt: retryCount,
|
|
224
|
+
delayMs,
|
|
225
|
+
reason: `${response.status} ${response.statusText}`,
|
|
226
|
+
status: response.status,
|
|
227
|
+
url
|
|
228
|
+
});
|
|
181
229
|
await delay(delayMs);
|
|
182
230
|
}
|
|
183
231
|
}
|
|
@@ -215,6 +263,96 @@ async function fetchAll(firstPage, fetchNext, options) {
|
|
|
215
263
|
return result;
|
|
216
264
|
}
|
|
217
265
|
|
|
266
|
+
// src/client/raw.ts
|
|
267
|
+
var RawClient = class {
|
|
268
|
+
constructor(baseUrl, getToken, fetchFn) {
|
|
269
|
+
this.baseUrl = baseUrl;
|
|
270
|
+
this.getToken = getToken;
|
|
271
|
+
this.fetchFn = fetchFn;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Make a raw HTTP request. Path template placeholders like `{id}` are substituted
|
|
275
|
+
* from matching keys in `query` before the remaining query params are appended to
|
|
276
|
+
* the URL as search parameters.
|
|
277
|
+
*/
|
|
278
|
+
async request({
|
|
279
|
+
method,
|
|
280
|
+
path,
|
|
281
|
+
query,
|
|
282
|
+
body,
|
|
283
|
+
token
|
|
284
|
+
}) {
|
|
285
|
+
let resolvedPath = path;
|
|
286
|
+
const remainingQuery = {};
|
|
287
|
+
if (query) {
|
|
288
|
+
for (const [key, value] of Object.entries(query)) {
|
|
289
|
+
if (value === void 0) continue;
|
|
290
|
+
const placeholder = `{${key}}`;
|
|
291
|
+
if (resolvedPath.includes(placeholder)) {
|
|
292
|
+
resolvedPath = resolvedPath.replace(new RegExp(`\\{${key}\\}`, "g"), String(value));
|
|
293
|
+
} else {
|
|
294
|
+
remainingQuery[key] = String(value);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const fullUrl = resolvedPath.startsWith("http") ? new URL(resolvedPath) : new URL(resolvedPath, this.baseUrl);
|
|
299
|
+
for (const [key, value] of Object.entries(remainingQuery)) {
|
|
300
|
+
fullUrl.searchParams.set(key, value);
|
|
301
|
+
}
|
|
302
|
+
const headers = {
|
|
303
|
+
Accept: "application/json"
|
|
304
|
+
};
|
|
305
|
+
const authToken = token ?? this.getToken();
|
|
306
|
+
if (authToken) {
|
|
307
|
+
headers["Authorization"] = `OAuth ${authToken}`;
|
|
308
|
+
}
|
|
309
|
+
let fetchBody;
|
|
310
|
+
if (body !== void 0) {
|
|
311
|
+
headers["Content-Type"] = "application/json";
|
|
312
|
+
fetchBody = JSON.stringify(body);
|
|
313
|
+
}
|
|
314
|
+
const response = await this.fetchFn(fullUrl.toString(), {
|
|
315
|
+
method,
|
|
316
|
+
headers,
|
|
317
|
+
body: fetchBody
|
|
318
|
+
});
|
|
319
|
+
const responseHeaders = {};
|
|
320
|
+
if (typeof response.headers.forEach === "function") {
|
|
321
|
+
response.headers.forEach((value, key) => {
|
|
322
|
+
responseHeaders[key] = value;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
let data;
|
|
326
|
+
const contentLength = response.headers.get("content-length");
|
|
327
|
+
if (response.status === 204 || contentLength === "0") {
|
|
328
|
+
data = void 0;
|
|
329
|
+
} else {
|
|
330
|
+
try {
|
|
331
|
+
data = await response.json();
|
|
332
|
+
} catch {
|
|
333
|
+
data = void 0;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { data, status: response.status, headers: responseHeaders };
|
|
337
|
+
}
|
|
338
|
+
/** GET shorthand */
|
|
339
|
+
get(path, params) {
|
|
340
|
+
return this.request({ method: "GET", path, query: params });
|
|
341
|
+
}
|
|
342
|
+
/** POST shorthand */
|
|
343
|
+
post(path, body) {
|
|
344
|
+
return this.request({ method: "POST", path, body });
|
|
345
|
+
}
|
|
346
|
+
/** PUT shorthand */
|
|
347
|
+
put(path, body) {
|
|
348
|
+
return this.request({ method: "PUT", path, body });
|
|
349
|
+
}
|
|
350
|
+
/** DELETE shorthand */
|
|
351
|
+
delete(path) {
|
|
352
|
+
return this.request({ method: "DELETE", path });
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
218
356
|
// src/client/SoundCloudClient.ts
|
|
219
357
|
function resolveToken(tokenGetter, explicit) {
|
|
220
358
|
const t = explicit ?? tokenGetter();
|
|
@@ -243,6 +381,8 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
243
381
|
likes;
|
|
244
382
|
/** Repost/unrepost actions (/reposts) */
|
|
245
383
|
reposts;
|
|
384
|
+
/** Low-level raw HTTP client — returns status/headers without throwing on non-2xx */
|
|
385
|
+
raw;
|
|
246
386
|
/**
|
|
247
387
|
* Creates a new SoundCloudClient instance.
|
|
248
388
|
*
|
|
@@ -254,7 +394,8 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
254
394
|
const retryConfig = {
|
|
255
395
|
maxRetries: config.maxRetries ?? 3,
|
|
256
396
|
retryBaseDelay: config.retryBaseDelay ?? 1e3,
|
|
257
|
-
onDebug: config.onDebug
|
|
397
|
+
onDebug: config.onDebug,
|
|
398
|
+
onRetry: config.onRetry
|
|
258
399
|
};
|
|
259
400
|
const refreshCtx = config.onTokenRefresh ? {
|
|
260
401
|
getToken,
|
|
@@ -283,6 +424,7 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
283
424
|
this.resolve = new _SoundCloudClient.Resolve(getToken, refreshCtx);
|
|
284
425
|
this.likes = new _SoundCloudClient.Likes(getToken, refreshCtx);
|
|
285
426
|
this.reposts = new _SoundCloudClient.Reposts(getToken, refreshCtx);
|
|
427
|
+
this.raw = new RawClient("https://api.soundcloud.com", getToken, config.fetch ?? globalThis.fetch);
|
|
286
428
|
}
|
|
287
429
|
/**
|
|
288
430
|
* Store an access token (and optionally refresh token) on this client instance.
|
|
@@ -1456,6 +1598,89 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
1456
1598
|
SoundCloudClient2.Reposts = Reposts;
|
|
1457
1599
|
})(SoundCloudClient || (SoundCloudClient = {}));
|
|
1458
1600
|
|
|
1601
|
+
// src/client/dedupe.ts
|
|
1602
|
+
var InFlightDeduper = class {
|
|
1603
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1604
|
+
/**
|
|
1605
|
+
* Return an existing in-flight promise for `key`, or start a new one via `factory`.
|
|
1606
|
+
* The entry is removed from the map once the promise settles (resolve or reject).
|
|
1607
|
+
*/
|
|
1608
|
+
add(key, factory) {
|
|
1609
|
+
const existing = this.inFlight.get(key);
|
|
1610
|
+
if (existing) return existing;
|
|
1611
|
+
const promise = factory().finally(() => {
|
|
1612
|
+
this.inFlight.delete(key);
|
|
1613
|
+
});
|
|
1614
|
+
this.inFlight.set(key, promise);
|
|
1615
|
+
return promise;
|
|
1616
|
+
}
|
|
1617
|
+
/** Number of currently in-flight requests */
|
|
1618
|
+
get size() {
|
|
1619
|
+
return this.inFlight.size;
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
|
|
1623
|
+
// src/client/registry.ts
|
|
1624
|
+
var IMPLEMENTED_OPERATIONS = [
|
|
1625
|
+
// Auth
|
|
1626
|
+
"post_oauth2_token",
|
|
1627
|
+
"delete_oauth2_token",
|
|
1628
|
+
// Me
|
|
1629
|
+
"get_me",
|
|
1630
|
+
"get_me_activities",
|
|
1631
|
+
"get_me_activities_own",
|
|
1632
|
+
"get_me_activities_tracks",
|
|
1633
|
+
"get_me_likes_tracks",
|
|
1634
|
+
"get_me_likes_playlists",
|
|
1635
|
+
"get_me_followings",
|
|
1636
|
+
"get_me_followings_tracks",
|
|
1637
|
+
"post_me_followings_user_id",
|
|
1638
|
+
"delete_me_followings_user_id",
|
|
1639
|
+
"get_me_followers",
|
|
1640
|
+
"get_me_playlists",
|
|
1641
|
+
"get_me_tracks",
|
|
1642
|
+
// Users
|
|
1643
|
+
"get_users_user_id",
|
|
1644
|
+
"get_users_user_id_followers",
|
|
1645
|
+
"get_users_user_id_followings",
|
|
1646
|
+
"get_users_user_id_tracks",
|
|
1647
|
+
"get_users_user_id_playlists",
|
|
1648
|
+
"get_users_user_id_likes_tracks",
|
|
1649
|
+
"get_users_user_id_likes_playlists",
|
|
1650
|
+
"get_users_user_id_web_profiles",
|
|
1651
|
+
// Tracks
|
|
1652
|
+
"get_tracks_track_id",
|
|
1653
|
+
"put_tracks_track_id",
|
|
1654
|
+
"delete_tracks_track_id",
|
|
1655
|
+
"get_tracks_track_id_comments",
|
|
1656
|
+
"post_tracks_track_id_comments",
|
|
1657
|
+
"get_tracks_track_id_likes",
|
|
1658
|
+
"get_tracks_track_id_reposts",
|
|
1659
|
+
"get_tracks_track_id_related",
|
|
1660
|
+
"get_tracks_track_id_streams",
|
|
1661
|
+
"post_likes_tracks_track_id",
|
|
1662
|
+
"delete_likes_tracks_track_id",
|
|
1663
|
+
"post_reposts_tracks_track_id",
|
|
1664
|
+
"delete_reposts_tracks_track_id",
|
|
1665
|
+
// Playlists
|
|
1666
|
+
"get_playlists_playlist_id",
|
|
1667
|
+
"post_playlists",
|
|
1668
|
+
"put_playlists_playlist_id",
|
|
1669
|
+
"delete_playlists_playlist_id",
|
|
1670
|
+
"get_playlists_playlist_id_tracks",
|
|
1671
|
+
"get_playlists_playlist_id_reposts",
|
|
1672
|
+
"post_likes_playlists_playlist_id",
|
|
1673
|
+
"delete_likes_playlists_playlist_id",
|
|
1674
|
+
"post_reposts_playlists_playlist_id",
|
|
1675
|
+
"delete_reposts_playlists_playlist_id",
|
|
1676
|
+
// Search
|
|
1677
|
+
"get_tracks",
|
|
1678
|
+
"get_users",
|
|
1679
|
+
"get_playlists",
|
|
1680
|
+
// Resolve
|
|
1681
|
+
"get_resolve"
|
|
1682
|
+
];
|
|
1683
|
+
|
|
1459
1684
|
// src/auth/getClientToken.ts
|
|
1460
1685
|
var getClientToken = (clientId, clientSecret) => {
|
|
1461
1686
|
const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
@@ -1748,6 +1973,6 @@ var unrepostPlaylist = async (token, playlistId) => {
|
|
|
1748
1973
|
// src/utils/widget.ts
|
|
1749
1974
|
var getSoundCloudWidgetUrl = (trackId) => `https%3A//api.soundcloud.com/tracks/${trackId}&show_teaser=false&color=%2300a99d&inverse=false&show_user=false&sharing=false&buying=false&liking=false&show_artwork=false&show_name=false`;
|
|
1750
1975
|
|
|
1751
|
-
export { SoundCloudClient, createPlaylist, createTrackComment, deletePlaylist, deleteTrack, fetchAll, followUser, generateCodeChallenge, generateCodeVerifier, getAuthorizationUrl, getClientToken, getFollowers, getFollowings, getMe, getMeActivities, getMeActivitiesOwn, getMeActivitiesTracks, getMeFollowers, getMeFollowings, getMeFollowingsTracks, getMeLikesPlaylists, getMeLikesTracks, getMePlaylists, getMeTracks, getPlaylist, getPlaylistReposts, getPlaylistTracks, getRelatedTracks, getSoundCloudWidgetUrl, getTrack, getTrackComments, getTrackLikes, getTrackReposts, getTrackStreams, getUser, getUserLikesPlaylists, getUserLikesTracks, getUserPlaylists, getUserToken, getUserTracks, getUserWebProfiles, likePlaylist, likeTrack, paginate, paginateItems, refreshUserToken, repostPlaylist, repostTrack, resolveUrl, scFetch, scFetchUrl, searchPlaylists, searchTracks, searchUsers, signOut, unfollowUser, unlikePlaylist, unlikeTrack, unrepostPlaylist, unrepostTrack, updatePlaylist, updateTrack };
|
|
1752
|
-
//# sourceMappingURL=chunk-
|
|
1753
|
-
//# sourceMappingURL=chunk-
|
|
1976
|
+
export { IMPLEMENTED_OPERATIONS, InFlightDeduper, RawClient, SoundCloudClient, createPlaylist, createTrackComment, deletePlaylist, deleteTrack, fetchAll, followUser, generateCodeChallenge, generateCodeVerifier, getAuthorizationUrl, getClientToken, getFollowers, getFollowings, getMe, getMeActivities, getMeActivitiesOwn, getMeActivitiesTracks, getMeFollowers, getMeFollowings, getMeFollowingsTracks, getMeLikesPlaylists, getMeLikesTracks, getMePlaylists, getMeTracks, getPlaylist, getPlaylistReposts, getPlaylistTracks, getRelatedTracks, getSoundCloudWidgetUrl, getTrack, getTrackComments, getTrackLikes, getTrackReposts, getTrackStreams, getUser, getUserLikesPlaylists, getUserLikesTracks, getUserPlaylists, getUserToken, getUserTracks, getUserWebProfiles, likePlaylist, likeTrack, paginate, paginateItems, refreshUserToken, repostPlaylist, repostTrack, resolveUrl, scFetch, scFetchUrl, searchPlaylists, searchTracks, searchUsers, signOut, unfollowUser, unlikePlaylist, unlikeTrack, unrepostPlaylist, unrepostTrack, updatePlaylist, updateTrack };
|
|
1977
|
+
//# sourceMappingURL=chunk-JLRQJWU5.mjs.map
|
|
1978
|
+
//# sourceMappingURL=chunk-JLRQJWU5.mjs.map
|