@manhgdev/soundcloud-package 0.1.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/index.js ADDED
@@ -0,0 +1,867 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, {
5
+ get: all[name],
6
+ enumerable: true,
7
+ configurable: true,
8
+ set: (newValue) => all[name] = () => newValue
9
+ });
10
+ };
11
+
12
+ // node_modules/query-string/base.js
13
+ var exports_base = {};
14
+ __export(exports_base, {
15
+ stringifyUrl: () => stringifyUrl,
16
+ stringify: () => stringify,
17
+ pick: () => pick,
18
+ parseUrl: () => parseUrl,
19
+ parse: () => parse,
20
+ extract: () => extract,
21
+ exclude: () => exclude
22
+ });
23
+
24
+ // node_modules/decode-uri-component/index.js
25
+ var token = "%[a-f0-9]{2}";
26
+ var singleMatcher = new RegExp("(" + token + ")|([^%]+?)", "gi");
27
+ var multiMatcher = new RegExp("(" + token + ")+", "gi");
28
+ function decodeComponents(components, split) {
29
+ try {
30
+ return [decodeURIComponent(components.join(""))];
31
+ } catch {}
32
+ if (components.length === 1) {
33
+ return components;
34
+ }
35
+ split = split || 1;
36
+ const left = components.slice(0, split);
37
+ const right = components.slice(split);
38
+ return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right));
39
+ }
40
+ function decode(input) {
41
+ try {
42
+ return decodeURIComponent(input);
43
+ } catch {
44
+ let tokens = input.match(singleMatcher) || [];
45
+ for (let i = 1;i < tokens.length; i++) {
46
+ input = decodeComponents(tokens, i).join("");
47
+ tokens = input.match(singleMatcher) || [];
48
+ }
49
+ return input;
50
+ }
51
+ }
52
+ function customDecodeURIComponent(input) {
53
+ const replaceMap = {
54
+ "%FE%FF": "��",
55
+ "%FF%FE": "��"
56
+ };
57
+ let match = multiMatcher.exec(input);
58
+ while (match) {
59
+ try {
60
+ replaceMap[match[0]] = decodeURIComponent(match[0]);
61
+ } catch {
62
+ const result = decode(match[0]);
63
+ if (result !== match[0]) {
64
+ replaceMap[match[0]] = result;
65
+ }
66
+ }
67
+ match = multiMatcher.exec(input);
68
+ }
69
+ replaceMap["%C2"] = "�";
70
+ const entries = Object.keys(replaceMap);
71
+ for (const key of entries) {
72
+ input = input.replace(new RegExp(key, "g"), replaceMap[key]);
73
+ }
74
+ return input;
75
+ }
76
+ function decodeUriComponent(encodedURI) {
77
+ if (typeof encodedURI !== "string") {
78
+ throw new TypeError("Expected `encodedURI` to be of type `string`, got `" + typeof encodedURI + "`");
79
+ }
80
+ try {
81
+ return decodeURIComponent(encodedURI);
82
+ } catch {
83
+ return customDecodeURIComponent(encodedURI);
84
+ }
85
+ }
86
+
87
+ // node_modules/split-on-first/index.js
88
+ function splitOnFirst(string, separator) {
89
+ if (!(typeof string === "string" && typeof separator === "string")) {
90
+ throw new TypeError("Expected the arguments to be of type `string`");
91
+ }
92
+ if (string === "" || separator === "") {
93
+ return [];
94
+ }
95
+ const separatorIndex = string.indexOf(separator);
96
+ if (separatorIndex === -1) {
97
+ return [];
98
+ }
99
+ return [
100
+ string.slice(0, separatorIndex),
101
+ string.slice(separatorIndex + separator.length)
102
+ ];
103
+ }
104
+
105
+ // node_modules/filter-obj/index.js
106
+ function includeKeys(object, predicate) {
107
+ const result = {};
108
+ if (Array.isArray(predicate)) {
109
+ for (const key of predicate) {
110
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
111
+ if (descriptor?.enumerable) {
112
+ Object.defineProperty(result, key, descriptor);
113
+ }
114
+ }
115
+ } else {
116
+ for (const key of Reflect.ownKeys(object)) {
117
+ const descriptor = Object.getOwnPropertyDescriptor(object, key);
118
+ if (descriptor.enumerable) {
119
+ const value = object[key];
120
+ if (predicate(key, value, object)) {
121
+ Object.defineProperty(result, key, descriptor);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+
129
+ // node_modules/query-string/base.js
130
+ var isNullOrUndefined = (value) => value === null || value === undefined;
131
+ var strictUriEncode = (string) => encodeURIComponent(string).replace(/[!'()*]/g, (x) => `%${x.charCodeAt(0).toString(16).toUpperCase()}`);
132
+ var encodeFragmentIdentifier = Symbol("encodeFragmentIdentifier");
133
+ function encoderForArrayFormat(options) {
134
+ switch (options.arrayFormat) {
135
+ case "index": {
136
+ return (key) => (result, value) => {
137
+ const index = result.length;
138
+ if (value === undefined || options.skipNull && value === null || options.skipEmptyString && value === "") {
139
+ return result;
140
+ }
141
+ if (value === null) {
142
+ return [
143
+ ...result,
144
+ [encode(key, options), "[", index, "]"].join("")
145
+ ];
146
+ }
147
+ return [
148
+ ...result,
149
+ [encode(key, options), "[", encode(index, options), "]=", encode(value, options)].join("")
150
+ ];
151
+ };
152
+ }
153
+ case "bracket": {
154
+ return (key) => (result, value) => {
155
+ if (value === undefined || options.skipNull && value === null || options.skipEmptyString && value === "") {
156
+ return result;
157
+ }
158
+ if (value === null) {
159
+ return [
160
+ ...result,
161
+ [encode(key, options), "[]"].join("")
162
+ ];
163
+ }
164
+ return [
165
+ ...result,
166
+ [encode(key, options), "[]=", encode(value, options)].join("")
167
+ ];
168
+ };
169
+ }
170
+ case "colon-list-separator": {
171
+ return (key) => (result, value) => {
172
+ if (value === undefined || options.skipNull && value === null || options.skipEmptyString && value === "") {
173
+ return result;
174
+ }
175
+ if (value === null) {
176
+ return [
177
+ ...result,
178
+ [encode(key, options), ":list="].join("")
179
+ ];
180
+ }
181
+ return [
182
+ ...result,
183
+ [encode(key, options), ":list=", encode(value, options)].join("")
184
+ ];
185
+ };
186
+ }
187
+ case "comma":
188
+ case "separator":
189
+ case "bracket-separator": {
190
+ const keyValueSep = options.arrayFormat === "bracket-separator" ? "[]=" : "=";
191
+ return (key) => (result, value) => {
192
+ if (value === undefined || options.skipNull && value === null || options.skipEmptyString && value === "") {
193
+ return result;
194
+ }
195
+ value = value === null ? "" : value;
196
+ if (result.length === 0) {
197
+ return [[encode(key, options), keyValueSep, encode(value, options)].join("")];
198
+ }
199
+ return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
200
+ };
201
+ }
202
+ default: {
203
+ return (key) => (result, value) => {
204
+ if (value === undefined || options.skipNull && value === null || options.skipEmptyString && value === "") {
205
+ return result;
206
+ }
207
+ if (value === null) {
208
+ return [
209
+ ...result,
210
+ encode(key, options)
211
+ ];
212
+ }
213
+ return [
214
+ ...result,
215
+ [encode(key, options), "=", encode(value, options)].join("")
216
+ ];
217
+ };
218
+ }
219
+ }
220
+ }
221
+ function parserForArrayFormat(options) {
222
+ let result;
223
+ switch (options.arrayFormat) {
224
+ case "index": {
225
+ return (key, value, accumulator) => {
226
+ result = /\[(\d*)]$/.exec(key);
227
+ key = key.replace(/\[\d*]$/, "");
228
+ if (!result) {
229
+ accumulator[key] = value;
230
+ return;
231
+ }
232
+ if (accumulator[key] === undefined) {
233
+ accumulator[key] = {};
234
+ }
235
+ accumulator[key][result[1]] = value;
236
+ };
237
+ }
238
+ case "bracket": {
239
+ return (key, value, accumulator) => {
240
+ result = /(\[])$/.exec(key);
241
+ key = key.replace(/\[]$/, "");
242
+ if (!result) {
243
+ accumulator[key] = value;
244
+ return;
245
+ }
246
+ if (accumulator[key] === undefined) {
247
+ accumulator[key] = [value];
248
+ return;
249
+ }
250
+ accumulator[key] = [...accumulator[key], value];
251
+ };
252
+ }
253
+ case "colon-list-separator": {
254
+ return (key, value, accumulator) => {
255
+ result = /(:list)$/.exec(key);
256
+ key = key.replace(/:list$/, "");
257
+ if (!result) {
258
+ accumulator[key] = value;
259
+ return;
260
+ }
261
+ if (accumulator[key] === undefined) {
262
+ accumulator[key] = [value];
263
+ return;
264
+ }
265
+ accumulator[key] = [...accumulator[key], value];
266
+ };
267
+ }
268
+ case "comma":
269
+ case "separator": {
270
+ return (key, value, accumulator) => {
271
+ const isArray = typeof value === "string" && value.includes(options.arrayFormatSeparator);
272
+ const isEncodedArray = typeof value === "string" && !isArray && decode2(value, options).includes(options.arrayFormatSeparator);
273
+ value = isEncodedArray ? decode2(value, options) : value;
274
+ const newValue = isArray || isEncodedArray ? value.split(options.arrayFormatSeparator).map((item) => decode2(item, options)) : value === null ? value : decode2(value, options);
275
+ accumulator[key] = newValue;
276
+ };
277
+ }
278
+ case "bracket-separator": {
279
+ return (key, value, accumulator) => {
280
+ const isArray = /(\[])$/.test(key);
281
+ key = key.replace(/\[]$/, "");
282
+ if (!isArray) {
283
+ accumulator[key] = value ? decode2(value, options) : value;
284
+ return;
285
+ }
286
+ const arrayValue = value === null ? [] : value.split(options.arrayFormatSeparator).map((item) => decode2(item, options));
287
+ if (accumulator[key] === undefined) {
288
+ accumulator[key] = arrayValue;
289
+ return;
290
+ }
291
+ accumulator[key] = [...accumulator[key], ...arrayValue];
292
+ };
293
+ }
294
+ default: {
295
+ return (key, value, accumulator) => {
296
+ if (accumulator[key] === undefined) {
297
+ accumulator[key] = value;
298
+ return;
299
+ }
300
+ accumulator[key] = [...[accumulator[key]].flat(), value];
301
+ };
302
+ }
303
+ }
304
+ }
305
+ function validateArrayFormatSeparator(value) {
306
+ if (typeof value !== "string" || value.length !== 1) {
307
+ throw new TypeError("arrayFormatSeparator must be single character string");
308
+ }
309
+ }
310
+ function encode(value, options) {
311
+ if (options.encode) {
312
+ return options.strict ? strictUriEncode(value) : encodeURIComponent(value);
313
+ }
314
+ return value;
315
+ }
316
+ function decode2(value, options) {
317
+ if (options.decode) {
318
+ return decodeUriComponent(value);
319
+ }
320
+ return value;
321
+ }
322
+ function keysSorter(input) {
323
+ if (Array.isArray(input)) {
324
+ return input.sort();
325
+ }
326
+ if (typeof input === "object") {
327
+ return keysSorter(Object.keys(input)).sort((a, b) => Number(a) - Number(b)).map((key) => input[key]);
328
+ }
329
+ return input;
330
+ }
331
+ function removeHash(input) {
332
+ const hashStart = input.indexOf("#");
333
+ if (hashStart !== -1) {
334
+ input = input.slice(0, hashStart);
335
+ }
336
+ return input;
337
+ }
338
+ function getHash(url) {
339
+ let hash = "";
340
+ const hashStart = url.indexOf("#");
341
+ if (hashStart !== -1) {
342
+ hash = url.slice(hashStart);
343
+ }
344
+ return hash;
345
+ }
346
+ function parseValue(value, options) {
347
+ if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === "string" && value.trim() !== "")) {
348
+ value = Number(value);
349
+ } else if (options.parseBooleans && value !== null && (value.toLowerCase() === "true" || value.toLowerCase() === "false")) {
350
+ value = value.toLowerCase() === "true";
351
+ }
352
+ return value;
353
+ }
354
+ function extract(input) {
355
+ input = removeHash(input);
356
+ const queryStart = input.indexOf("?");
357
+ if (queryStart === -1) {
358
+ return "";
359
+ }
360
+ return input.slice(queryStart + 1);
361
+ }
362
+ function parse(query, options) {
363
+ options = {
364
+ decode: true,
365
+ sort: true,
366
+ arrayFormat: "none",
367
+ arrayFormatSeparator: ",",
368
+ parseNumbers: false,
369
+ parseBooleans: false,
370
+ ...options
371
+ };
372
+ validateArrayFormatSeparator(options.arrayFormatSeparator);
373
+ const formatter = parserForArrayFormat(options);
374
+ const returnValue = Object.create(null);
375
+ if (typeof query !== "string") {
376
+ return returnValue;
377
+ }
378
+ query = query.trim().replace(/^[?#&]/, "");
379
+ if (!query) {
380
+ return returnValue;
381
+ }
382
+ for (const parameter of query.split("&")) {
383
+ if (parameter === "") {
384
+ continue;
385
+ }
386
+ const parameter_ = options.decode ? parameter.replace(/\+/g, " ") : parameter;
387
+ let [key, value] = splitOnFirst(parameter_, "=");
388
+ if (key === undefined) {
389
+ key = parameter_;
390
+ }
391
+ value = value === undefined ? null : ["comma", "separator", "bracket-separator"].includes(options.arrayFormat) ? value : decode2(value, options);
392
+ formatter(decode2(key, options), value, returnValue);
393
+ }
394
+ for (const [key, value] of Object.entries(returnValue)) {
395
+ if (typeof value === "object" && value !== null) {
396
+ for (const [key2, value2] of Object.entries(value)) {
397
+ value[key2] = parseValue(value2, options);
398
+ }
399
+ } else {
400
+ returnValue[key] = parseValue(value, options);
401
+ }
402
+ }
403
+ if (options.sort === false) {
404
+ return returnValue;
405
+ }
406
+ return (options.sort === true ? Object.keys(returnValue).sort() : Object.keys(returnValue).sort(options.sort)).reduce((result, key) => {
407
+ const value = returnValue[key];
408
+ result[key] = Boolean(value) && typeof value === "object" && !Array.isArray(value) ? keysSorter(value) : value;
409
+ return result;
410
+ }, Object.create(null));
411
+ }
412
+ function stringify(object, options) {
413
+ if (!object) {
414
+ return "";
415
+ }
416
+ options = {
417
+ encode: true,
418
+ strict: true,
419
+ arrayFormat: "none",
420
+ arrayFormatSeparator: ",",
421
+ ...options
422
+ };
423
+ validateArrayFormatSeparator(options.arrayFormatSeparator);
424
+ const shouldFilter = (key) => options.skipNull && isNullOrUndefined(object[key]) || options.skipEmptyString && object[key] === "";
425
+ const formatter = encoderForArrayFormat(options);
426
+ const objectCopy = {};
427
+ for (const [key, value] of Object.entries(object)) {
428
+ if (!shouldFilter(key)) {
429
+ objectCopy[key] = value;
430
+ }
431
+ }
432
+ const keys = Object.keys(objectCopy);
433
+ if (options.sort !== false) {
434
+ keys.sort(options.sort);
435
+ }
436
+ return keys.map((key) => {
437
+ const value = object[key];
438
+ if (value === undefined) {
439
+ return "";
440
+ }
441
+ if (value === null) {
442
+ return encode(key, options);
443
+ }
444
+ if (Array.isArray(value)) {
445
+ if (value.length === 0 && options.arrayFormat === "bracket-separator") {
446
+ return encode(key, options) + "[]";
447
+ }
448
+ return value.reduce(formatter(key), []).join("&");
449
+ }
450
+ return encode(key, options) + "=" + encode(value, options);
451
+ }).filter((x) => x.length > 0).join("&");
452
+ }
453
+ function parseUrl(url, options) {
454
+ options = {
455
+ decode: true,
456
+ ...options
457
+ };
458
+ let [url_, hash] = splitOnFirst(url, "#");
459
+ if (url_ === undefined) {
460
+ url_ = url;
461
+ }
462
+ return {
463
+ url: url_?.split("?")?.[0] ?? "",
464
+ query: parse(extract(url), options),
465
+ ...options && options.parseFragmentIdentifier && hash ? { fragmentIdentifier: decode2(hash, options) } : {}
466
+ };
467
+ }
468
+ function stringifyUrl(object, options) {
469
+ options = {
470
+ encode: true,
471
+ strict: true,
472
+ [encodeFragmentIdentifier]: true,
473
+ ...options
474
+ };
475
+ const url = removeHash(object.url).split("?")[0] || "";
476
+ const queryFromUrl = extract(object.url);
477
+ const query = {
478
+ ...parse(queryFromUrl, { sort: false }),
479
+ ...object.query
480
+ };
481
+ let queryString = stringify(query, options);
482
+ if (queryString) {
483
+ queryString = `?${queryString}`;
484
+ }
485
+ let hash = getHash(object.url);
486
+ if (object.fragmentIdentifier) {
487
+ const urlObjectForFragmentEncode = new URL(url);
488
+ urlObjectForFragmentEncode.hash = object.fragmentIdentifier;
489
+ hash = options[encodeFragmentIdentifier] ? urlObjectForFragmentEncode.hash : `#${object.fragmentIdentifier}`;
490
+ }
491
+ return `${url}${queryString}${hash}`;
492
+ }
493
+ function pick(input, filter, options) {
494
+ options = {
495
+ parseFragmentIdentifier: true,
496
+ [encodeFragmentIdentifier]: false,
497
+ ...options
498
+ };
499
+ const { url, query, fragmentIdentifier } = parseUrl(input, options);
500
+ return stringifyUrl({
501
+ url,
502
+ query: includeKeys(query, filter),
503
+ fragmentIdentifier
504
+ }, options);
505
+ }
506
+ function exclude(input, filter, options) {
507
+ const exclusionFilter = Array.isArray(filter) ? (key) => !filter.includes(key) : (key, value) => !filter(key, value);
508
+ return pick(input, exclusionFilter, options);
509
+ }
510
+
511
+ // node_modules/query-string/index.js
512
+ var query_string_default = exports_base;
513
+
514
+ // src/modules/search.js
515
+ class SearchModule {
516
+ constructor(api) {
517
+ this.api = api;
518
+ }
519
+ all(query, options = {}) {
520
+ return this.api.request("/search", {
521
+ q: query,
522
+ ...options
523
+ });
524
+ }
525
+ tracks(query, options = {}) {
526
+ const params = {
527
+ q: query,
528
+ ...options
529
+ };
530
+ return this.api.request("/search/tracks", params);
531
+ }
532
+ users(query, options = {}) {
533
+ return this.api.request("/search/users", {
534
+ q: query,
535
+ ...options
536
+ });
537
+ }
538
+ albums(query, options = {}) {
539
+ return this.api.request("/search/albums", {
540
+ q: query,
541
+ ...options
542
+ });
543
+ }
544
+ playlists(query, options = {}) {
545
+ return this.api.request("/search/playlists_without_albums", {
546
+ q: query,
547
+ ...options
548
+ });
549
+ }
550
+ byGenre(genre, options = {}) {
551
+ return this.tracks("*", {
552
+ "filter.genre_or_tag": genre,
553
+ sort: "popular",
554
+ ...options
555
+ });
556
+ }
557
+ }
558
+ var search_default = SearchModule;
559
+
560
+ // src/modules/tracks.js
561
+ class TracksModule {
562
+ constructor(api) {
563
+ this.api = api;
564
+ }
565
+ getMultiple(ids) {
566
+ if (!Array.isArray(ids)) {
567
+ throw new Error("IDs must be an array");
568
+ }
569
+ return this.api.request("/tracks", {
570
+ ids: ids.join(",")
571
+ });
572
+ }
573
+ getComments(trackId, options = {}) {
574
+ return this.api.request(`/tracks/${trackId}/comments`, options);
575
+ }
576
+ getRelated(trackId, options = {}) {
577
+ return this.api.request(`/tracks/${trackId}/related`, options);
578
+ }
579
+ async getStreamUrl(trackId) {
580
+ try {
581
+ const trackInfo = await this.api.request(`/tracks/${trackId}`);
582
+ if (!trackInfo || !trackInfo.media || !trackInfo.media.transcodings) {
583
+ throw new Error("Track streaming information not available");
584
+ }
585
+ const hlsTranscoding = trackInfo.media.transcodings.find((t) => t.format.protocol === "hls" && t.format.mime_type === "audio/mpeg");
586
+ if (!hlsTranscoding) {
587
+ throw new Error("HLS streaming not available for this track");
588
+ }
589
+ const mediaUrl = hlsTranscoding.url;
590
+ const mediaTranscodingId = mediaUrl.split("/").pop();
591
+ const streamInfo = await this.api.request(`/media/soundcloud:tracks:${trackId}/${mediaTranscodingId}/stream/hls`, { track_authorization: trackInfo.track_authorization });
592
+ return streamInfo.url;
593
+ } catch (error) {
594
+ throw new Error(`Failed to get stream URL: ${error.message}`);
595
+ }
596
+ }
597
+ }
598
+ var tracks_default = TracksModule;
599
+
600
+ // src/modules/users.js
601
+ class UsersModule {
602
+ constructor(api) {
603
+ this.api = api;
604
+ }
605
+ getUser(userId) {
606
+ return this.api.request(`/users/${userId}`);
607
+ }
608
+ getSpotlight(userId) {
609
+ return this.api.request(`/users/${userId}/spotlight`);
610
+ }
611
+ getFeaturedProfiles(userId) {
612
+ return this.api.request(`/users/${userId}/featured-profiles`);
613
+ }
614
+ getLikes(userId, options = {}) {
615
+ return this.api.request(`/users/${userId}/likes`, options);
616
+ }
617
+ getFollowings(userId, options = {}) {
618
+ return this.api.request(`/users/${userId}/followings`, options);
619
+ }
620
+ getRelatedArtists(userId, options = {}) {
621
+ return this.api.request(`/users/${userId}/relatedartists`, options);
622
+ }
623
+ getComments(userId, options = {}) {
624
+ return this.api.request(`/users/${userId}/comments`, options);
625
+ }
626
+ getStream(userId, options = {}) {
627
+ return this.api.request(`/stream/users/${userId}`, options);
628
+ }
629
+ getTopTracks(userId, options = {}) {
630
+ return this.api.request(`/users/${userId}/toptracks`, options);
631
+ }
632
+ getTracks(userId, options = {}) {
633
+ return this.api.request(`/users/${userId}/tracks`, options);
634
+ }
635
+ getPlaylists(userId, options = {}) {
636
+ return this.api.request(`/users/${userId}/playlists_without_albums`, options);
637
+ }
638
+ getWebProfiles(userId) {
639
+ return this.api.request(`/users/soundcloud:users:${userId}/web-profiles`);
640
+ }
641
+ }
642
+ var users_default = UsersModule;
643
+
644
+ // src/modules/playlists.js
645
+ class PlaylistsModule {
646
+ constructor(api) {
647
+ this.api = api;
648
+ }
649
+ getPlaylist(playlistId, options = {}) {
650
+ return this.api.request(`/playlists/${playlistId}`, options);
651
+ }
652
+ getLikers(playlistId, options = {}) {
653
+ return this.api.request(`/playlists/${playlistId}/likers`, options);
654
+ }
655
+ getReposters(playlistId, options = {}) {
656
+ return this.api.request(`/playlists/${playlistId}/reposters`, options);
657
+ }
658
+ getByGenre(genre, options = {}) {
659
+ return this.api.request("/playlists/discovery", {
660
+ tag: genre,
661
+ ...options
662
+ });
663
+ }
664
+ }
665
+ var playlists_default = PlaylistsModule;
666
+
667
+ // src/modules/media.js
668
+ class MediaModule {
669
+ constructor(api) {
670
+ this.api = api;
671
+ }
672
+ getStreamURL(mediaUrl, trackAuthorization, clientId) {
673
+ return `${mediaUrl}?client_id=${clientId}&track_authorization=${trackAuthorization}`;
674
+ }
675
+ async getPlaybackUrl(trackId) {
676
+ try {
677
+ return await this.getMediaUrl(trackId, "hls");
678
+ } catch (error) {
679
+ throw new Error(`Failed to get playback URL: ${error.message}`);
680
+ }
681
+ }
682
+ async getDownloadUrl(trackId) {
683
+ try {
684
+ return await this.getMediaUrl(trackId, "progressive");
685
+ } catch (error) {
686
+ throw new Error(`Failed to get download URL: ${error.message}`);
687
+ }
688
+ }
689
+ async getMediaUrl(trackId, protocol = "hls") {
690
+ try {
691
+ const track = await this.api.tracks.getMultiple([trackId]);
692
+ if (!track || !track.length || !track[0]) {
693
+ throw new Error("Track not found");
694
+ }
695
+ const trackData = track[0];
696
+ if (!trackData.media || !trackData.media.transcodings || !trackData.media.transcodings.length) {
697
+ throw new Error("No media transcodings available for this track");
698
+ }
699
+ const transcoding = trackData.media.transcodings.find((t) => t.format.protocol === protocol && t.format.mime_type === "audio/mpeg");
700
+ if (!transcoding) {
701
+ throw new Error(`No suitable ${protocol} media transcoding found`);
702
+ }
703
+ const mediaUrl = transcoding.url;
704
+ return this.getStreamURL(mediaUrl, trackData.track_authorization, this.api.clientId);
705
+ } catch (error) {
706
+ throw new Error(`Failed to get media URL: ${error.message}`);
707
+ }
708
+ }
709
+ }
710
+ var media_default = MediaModule;
711
+
712
+ // src/modules/discover.js
713
+ class DiscoverModule {
714
+ constructor(api) {
715
+ this.api = api;
716
+ }
717
+ getHomeContent() {
718
+ return this.api.request("/mixed-selections");
719
+ }
720
+ getRecentTracks(genre = "all genres") {
721
+ return this.api.request(`/recent-tracks/${encodeURIComponent(genre)}`);
722
+ }
723
+ getRecentTracksByCountry() {
724
+ return this.api.request("/recent-tracks/country");
725
+ }
726
+ }
727
+ var discover_default = DiscoverModule;
728
+
729
+ // src/index.js
730
+ class SoundCloudAPI {
731
+ constructor(options = {}) {
732
+ this.clientId = options.clientId;
733
+ this.baseURL = "https://api-v2.soundcloud.com";
734
+ this.appVersion = options.appVersion || "1753870647";
735
+ this.appLocale = options.appLocale || "en";
736
+ this.autoFetchClientId = options.autoFetchClientId !== false;
737
+ this.clientIdCache = {
738
+ value: this.clientId,
739
+ expirationTime: this.clientId ? Date.now() + 360000 : 0
740
+ };
741
+ this.clientIdPromise = null;
742
+ this.headers = {
743
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
744
+ Accept: "application/json, text/javascript, */*; q=0.01",
745
+ Origin: "https://soundcloud.com",
746
+ Referer: "https://soundcloud.com/",
747
+ "Accept-Language": "en,vi;q=0.9,en-US;q=0.8"
748
+ };
749
+ this.search = new search_default(this);
750
+ this.tracks = new tracks_default(this);
751
+ this.users = new users_default(this);
752
+ this.playlists = new playlists_default(this);
753
+ this.media = new media_default(this);
754
+ this.discover = new discover_default(this);
755
+ if (this.autoFetchClientId) {
756
+ this._initClientId();
757
+ }
758
+ }
759
+ async _initClientId() {
760
+ try {
761
+ const newClientId = await this.getClientId();
762
+ } catch (error) {
763
+ console.error("[SOUNDCLOUD] Error initializing client ID:", error.message);
764
+ }
765
+ }
766
+ async getClientId() {
767
+ if (this.autoFetchClientId && (!this.clientId || Date.now() > this.clientIdCache.expirationTime)) {
768
+ try {
769
+ if (this.clientIdPromise) {
770
+ return await this.clientIdPromise;
771
+ }
772
+ this.clientIdPromise = this.fetchClientIdFromWeb();
773
+ const newClientId = await this.clientIdPromise;
774
+ if (newClientId) {
775
+ this.clientId = newClientId;
776
+ this.clientIdCache = {
777
+ value: newClientId,
778
+ expirationTime: Date.now() + 180000
779
+ };
780
+ }
781
+ this.clientIdPromise = null;
782
+ } catch (error) {
783
+ this.clientIdPromise = null;
784
+ console.error("[SOUNDCLOUD] Error fetching client ID:", error.message);
785
+ }
786
+ }
787
+ return this.clientId;
788
+ }
789
+ async fetchClientIdFromWeb() {
790
+ const webURL = "https://www.soundcloud.com/";
791
+ let script = "";
792
+ try {
793
+ console.log("[SOUNDCLOUD] Fetching client ID from web...");
794
+ const response = await fetch(webURL, {
795
+ headers: this.headers
796
+ });
797
+ if (!response.ok) {
798
+ throw new Error(`Failed to fetch SoundCloud page: ${response.status}`);
799
+ }
800
+ const html = await response.text();
801
+ const scriptUrlRegex = /(?!<script crossorigin src=")https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*\.js)(?=">)/g;
802
+ const urls = html.match(scriptUrlRegex) || [];
803
+ if (urls.length === 0) {
804
+ throw new Error("No script URLs found in SoundCloud page");
805
+ }
806
+ for (let i = urls.length - 1;i >= 0; i--) {
807
+ const scriptUrl = urls[i];
808
+ const scriptResponse = await fetch(scriptUrl, {
809
+ headers: this.headers
810
+ });
811
+ if (!scriptResponse.ok)
812
+ continue;
813
+ script = await scriptResponse.text();
814
+ if (script.includes(',client_id:"')) {
815
+ const match = script.match(/,client_id:"(\w+)"/);
816
+ if (match && match[1]) {
817
+ const clientId = match[1];
818
+ return clientId;
819
+ }
820
+ }
821
+ }
822
+ throw new Error("Client ID not found in any script");
823
+ } catch (error) {
824
+ console.error("[SOUNDCLOUD] Error in fetchClientIdFromWeb:", error.message);
825
+ throw error;
826
+ }
827
+ }
828
+ async request(endpoint, params = {}) {
829
+ const clientId = await this.getClientId();
830
+ if (!clientId) {
831
+ throw new Error("SoundCloud API Error: No client ID available. Please provide a client ID or enable autoFetchClientId.");
832
+ }
833
+ const defaultParams = {
834
+ client_id: clientId,
835
+ app_version: this.appVersion,
836
+ app_locale: this.appLocale
837
+ };
838
+ const queryParams = query_string_default.stringify({
839
+ ...defaultParams,
840
+ ...params
841
+ });
842
+ const url = `${this.baseURL}${endpoint}?${queryParams}`;
843
+ try {
844
+ const response = await fetch(url, {
845
+ method: "GET",
846
+ headers: this.headers
847
+ });
848
+ if (!response.ok) {
849
+ const errorData = await response.json().catch(() => ({}));
850
+ throw new Error(`SoundCloud API Error: ${response.status} - ${JSON.stringify(errorData)}`);
851
+ }
852
+ return await response.json();
853
+ } catch (error) {
854
+ if (error.name === "AbortError") {
855
+ throw new Error("SoundCloud API Error: Request was aborted");
856
+ } else if (error.name === "TypeError") {
857
+ throw new Error("SoundCloud API Error: Network error");
858
+ } else {
859
+ throw error;
860
+ }
861
+ }
862
+ }
863
+ }
864
+ var src_default = SoundCloudAPI;
865
+ export {
866
+ src_default as default
867
+ };