@zibot/scdl
Version:
Soucloud download
307 lines (205 loc) • 7.92 kB
Markdown
# @zibot/scdl
A tiny, promise-based Node.js client for searching SoundCloud, fetching track/playlist details, and downloading tracks as readable
streams.
- **Search** tracks, playlists, and users
- **Inspect** rich metadata for tracks and playlists
- **Download** a track stream (high/low quality)
- **Discover** related tracks by URL or ID
> ⚠️ Respect SoundCloud’s Terms of Service and creator rights. This library is for personal/educational use; don’t redistribute
> copyrighted content without permission.
## Installation
```bash
npm i @zibot/scdl
# or
yarn add @zibot/scdl
# or
pnpm add @zibot/scdl
```
**Runtime:** Node.js 18+ is recommended (built-in `fetch`/WHATWG streams). Works with ESM or CommonJS.
## Quick Start
### ESM
```ts
import SoundCloud from "@zibot/scdl";
const sc = new SoundCloud({ init: true }); // auto-initialize clientId
(async () => {
// Search tracks
const results = await sc.search({ query: "lofi hip hop", limit: 5, type: "tracks" });
console.log(results);
// Track details
const track = await sc.getTrackDetails("https://soundcloud.com/user/track-slug");
console.log(track.title, track.user.username);
// Download a track (Readable stream)
const stream = await sc.downloadTrack("https://soundcloud.com/user/track-slug", { quality: "high" });
stream.pipe(process.stdout); // or pipe to fs.createWriteStream("track.mp3")
// Related tracks
const related = await sc.getRelatedTracks(track.id, { limit: 10 });
console.log(related.map((t) => t.title));
})();
```
### CommonJS
```js
const SoundCloud = require("@zibot/scdl");
const fs = require("node:fs");
const sc = new SoundCloud({ init: true });
(async () => {
const stream = await sc.downloadTrack("https://soundcloud.com/user/track-slug");
stream.pipe(fs.createWriteStream("track.mp3"));
})();
```
## Initialization
The client can retrieve a valid `clientId` automatically.
```ts
// Option A: auto-init (recommended)
const sc = new SoundCloud({ init: true });
// Option B: manual init
const sc = new SoundCloud();
await sc.init(); // retrieves clientId
```
> You usually only need to call `init()` once per process. If you get authentication errors later, re-calling `init()` may refresh
> the client ID.
## API
### `new SoundCloud(options?)`
Create a client.
- `options.init?: boolean` – if `true`, calls `init()` internally.
**Properties**
- `clientId: string | null` – resolved after `init()`
- `apiBaseUrl: string` – internal base URL used for API calls
### `init(): Promise<void>`
Initialize the client (retrieve `clientId`). Call this if you didn’t pass `{ init: true }`.
### `search(options: SearchOptions): Promise<(Track | Playlist | User)[]>`
Search SoundCloud.
**Parameters – `SearchOptions`**
- `query: string` – search text
- `limit?: number` – default depends on endpoint (commonly 10–20)
- `offset?: number` – for pagination
- `type?: "all" | "tracks" | "playlists" | "users"` – filter result kinds (default `"all"`)
**Usage**
```ts
// Top tracks
const tracks = await sc.search({ query: "ambient study", type: "tracks", limit: 10 });
// Mixed kinds (tracks/playlists/users)
const mixed = await sc.search({ query: "chill", type: "all", limit: 5, offset: 5 });
```
### `getTrackDetails(url: string): Promise<Track>`
Get rich metadata for a single track by its public URL.
```ts
const t = await sc.getTrackDetails("https://soundcloud.com/artist/track");
console.log({
id: t.id,
title: t.title,
url: t.permalink_url,
});
```
### `getPlaylistDetails(url: string): Promise<Playlist>`
Fetch playlist metadata and contained tracks.
```ts
const pl = await sc.getPlaylistDetails("https://soundcloud.com/artist/sets/playlist-slug");
console.log(pl.title, pl.tracks.length);
```
### `downloadTrack(url: string, options?: DownloadOptions): Promise<Readable>`
Download a track as a Node `Readable` stream.
**Parameters – `DownloadOptions`**
- `quality?: "high" | "low"` – choose available transcoding (default implementation prefers higher quality when available)
**Examples**
```ts
import fs from "node:fs";
const read = await sc.downloadTrack("https://soundcloud.com/user/track", { quality: "high" });
await new Promise((resolve, reject) => {
read.pipe(fs.createWriteStream("track.ts")).on("finish", resolve).on("error", reject);
});
```
Pipe to any writable destination (stdout, HTTP response, cloud storage SDKs, etc.).
### `getRelatedTracks(track: string | number, opts?: { limit?: number; offset?: number }): Promise<Track[]>`
Fetch tracks related to a given track by **URL** or **numeric ID**.
```ts
// by URL
const relByUrl = await sc.getRelatedTracks("https://soundcloud.com/artist/track", { limit: 6 });
// by ID
const base = await sc.getTrackDetails("https://soundcloud.com/artist/track");
const relById = await sc.getRelatedTracks(base.id, { limit: 6 });
```
## Types
```ts
export interface SearchOptions {
query: string;
limit?: number;
offset?: number;
type?: "all" | "tracks" | "playlists" | "users";
}
export interface DownloadOptions {
quality?: "high" | "low";
}
export interface User {
id: number;
username: string;
followers_count: number;
track_count: number;
}
```
These types are exported for TypeScript consumers.
## Examples
### Save the highest-quality stream to disk
```ts
import fs from "node:fs";
import SoundCloud from "@zibot/scdl";
const sc = new SoundCloud({ init: true });
async function save(url: string, file: string) {
const stream = await sc.downloadTrack(url, { quality: "high" });
await new Promise<void>((resolve, reject) => {
stream.pipe(fs.createWriteStream(file)).on("finish", resolve).on("error", reject);
});
}
save("https://soundcloud.com/user/track", "output.ts");
```
### Basic search → pick first result → download
```ts
const [first] = await sc.search({ query: "deep house 2024", type: "tracks", limit: 1 });
if (first && "url" in first) {
const s = await sc.downloadTrack(first.url);
s.pipe(process.stdout);
}
```
### Paginate results
```ts
const page1 = await sc.search({ query: "vaporwave", type: "tracks", limit: 20, offset: 0 });
const page2 = await sc.search({ query: "vaporwave", type: "tracks", limit: 20, offset: 20 });
```
## Error Handling & Tips
- **Initialization:** If a method throws due to missing/expired `clientId`, call `await sc.init()` and retry.
- **Quality selection:** Not all tracks expose multiple transcodings; the library will fall back when needed.
- **Rate limits / 429:** Back off and retry with an exponential strategy.
- **Private/geo-restricted tracks:** Details/downloads may be unavailable.
- **Networking:** Wrap downloads with proper error and close handlers to avoid dangling file descriptors.
## FAQ
**Q: Can I use this in the browser?** This package targets Node.js (it returns Node `Readable`). Browser use is not supported.
**Q: What audio format do I get?** Whatever the selected transcoding provides (commonly progressive MP3 or HLS AAC). You may need
to remux/encode if you require a specific container/codec.
**Q: Do I need my own client ID?** The client auto-discovers a valid `clientId`. If discovery fails due to upstream changes,
update the package to the latest version.
## Contributing
PRs and issues are welcome! Please include:
- A clear description of the change
- Repro steps (for bugs)
- Tests where possible
## License
MIT © Zibot
## Disclaimer
This project is not affiliated with SoundCloud. Use responsibly and comply with all applicable laws and SoundCloud’s Terms of
Service.