soundcloud-api-ts 1.11.1 → 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-GTSVJU3P.js → chunk-D7AF372V.js} +243 -11
- package/dist/chunk-D7AF372V.js.map +1 -0
- package/dist/{chunk-DGZEPXIQ.mjs → chunk-JLRQJWU5.mjs} +241 -12
- 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-DGZEPXIQ.mjs.map +0 -1
- package/dist/chunk-GTSVJU3P.js.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.
|
|
@@ -417,13 +559,17 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
417
559
|
* @see https://developers.soundcloud.com/docs/api/explorer/open-api#/oauth2/post_oauth2_token
|
|
418
560
|
*/
|
|
419
561
|
async getClientToken() {
|
|
562
|
+
const basicAuth = Buffer.from(
|
|
563
|
+
`${this.config.clientId}:${this.config.clientSecret}`
|
|
564
|
+
).toString("base64");
|
|
420
565
|
return this.fetch({
|
|
421
566
|
path: "/oauth/token",
|
|
422
567
|
method: "POST",
|
|
568
|
+
headers: {
|
|
569
|
+
Authorization: `Basic ${basicAuth}`
|
|
570
|
+
},
|
|
423
571
|
body: new URLSearchParams({
|
|
424
|
-
grant_type: "client_credentials"
|
|
425
|
-
client_id: this.config.clientId,
|
|
426
|
-
client_secret: this.config.clientSecret
|
|
572
|
+
grant_type: "client_credentials"
|
|
427
573
|
})
|
|
428
574
|
});
|
|
429
575
|
}
|
|
@@ -1452,6 +1598,89 @@ var SoundCloudClient = class _SoundCloudClient {
|
|
|
1452
1598
|
SoundCloudClient2.Reposts = Reposts;
|
|
1453
1599
|
})(SoundCloudClient || (SoundCloudClient = {}));
|
|
1454
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
|
+
|
|
1455
1684
|
// src/auth/getClientToken.ts
|
|
1456
1685
|
var getClientToken = (clientId, clientSecret) => {
|
|
1457
1686
|
const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
@@ -1744,6 +1973,6 @@ var unrepostPlaylist = async (token, playlistId) => {
|
|
|
1744
1973
|
// src/utils/widget.ts
|
|
1745
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`;
|
|
1746
1975
|
|
|
1747
|
-
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 };
|
|
1748
|
-
//# sourceMappingURL=chunk-
|
|
1749
|
-
//# 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
|