@zibot/scdl 0.0.1
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/README.md +118 -0
- package/index.d.ts +78 -0
- package/index.js +124 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @zibot/scdl
|
|
2
|
+
|
|
3
|
+
@zibot/scdl là một module JavaScript hỗ trợ tải, tìm kiếm, và quản lý dữ liệu từ SoundCloud thông qua API. Module này cung cấp các
|
|
4
|
+
phương thức tiện lợi để xử lý track, playlist, và tải nội dung.
|
|
5
|
+
|
|
6
|
+
## Cài đặt
|
|
7
|
+
|
|
8
|
+
Bạn có thể cài đặt module này bằng npm hoặc yarn:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @zibot/scdl
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
hoặc
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yarn add @zibot/scdl
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Sử dụng
|
|
21
|
+
|
|
22
|
+
### Khởi tạo
|
|
23
|
+
|
|
24
|
+
Đầu tiên, bạn cần khởi tạo lớp `SoundCloud` để sử dụng các chức năng của module:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const { SoundCloud } = require("@zibot/scdl");
|
|
28
|
+
|
|
29
|
+
(async () => {
|
|
30
|
+
const scdl = new SoundCloud({ init: true }); // Tự động lấy client ID
|
|
31
|
+
await scdl.init(); // Đảm bảo client ID được khởi tạo
|
|
32
|
+
})();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### Các phương thức
|
|
38
|
+
|
|
39
|
+
#### **`searchTracks(options)`**
|
|
40
|
+
|
|
41
|
+
Tìm kiếm các track trên SoundCloud.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const tracks = await scdl.searchTracks({ query: "chill", limit: 5 });
|
|
45
|
+
console.log(tracks);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Tham số:**
|
|
49
|
+
|
|
50
|
+
- `query` (bắt buộc): Từ khóa để tìm kiếm.
|
|
51
|
+
- `limit` (mặc định: `20`): Số lượng track tối đa.
|
|
52
|
+
- `offset` (mặc định: `0`): Vị trí bắt đầu.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
#### **`getTrack(url)`**
|
|
57
|
+
|
|
58
|
+
Lấy thông tin chi tiết về một track.
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
const track = await scdl.getTrack("https://soundcloud.com/user/song");
|
|
62
|
+
console.log(track);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Tham số:**
|
|
66
|
+
|
|
67
|
+
- `url` (bắt buộc): URL của track.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
#### **`getPlaylist(url)`**
|
|
72
|
+
|
|
73
|
+
Lấy thông tin chi tiết về một playlist.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const playlist = await scdl.getPlaylist("https://soundcloud.com/user/playlist");
|
|
77
|
+
console.log(playlist);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Tham số:**
|
|
81
|
+
|
|
82
|
+
- `url` (bắt buộc): URL của playlist.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
#### **`downloadTrack(url, options)`**
|
|
87
|
+
|
|
88
|
+
Tải một track từ SoundCloud.
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const stream = await scdl.downloadTrack("https://soundcloud.com/user/song");
|
|
92
|
+
stream.pipe(fs.createWriteStream("track.mp3"));
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Tham số:**
|
|
96
|
+
|
|
97
|
+
- `url` (bắt buộc): URL của track.
|
|
98
|
+
- `options` (tùy chọn): Cấu hình tải xuống.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### Lưu ý
|
|
103
|
+
|
|
104
|
+
- Trước khi sử dụng các phương thức như `searchTracks`, `getTrack`, hoặc `downloadTrack`, cần đảm bảo rằng phương thức `init()` đã
|
|
105
|
+
hoàn thành để lấy `clientId`.
|
|
106
|
+
- Nếu `clientId` không được khởi tạo, các phương thức này sẽ ném lỗi.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Đóng góp
|
|
111
|
+
|
|
112
|
+
Nếu bạn muốn đóng góp cho dự án này, vui lòng tạo một pull request hoặc mở một issue trên GitHub.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Giấy phép
|
|
117
|
+
|
|
118
|
+
Dự án này được cấp phép theo giấy phép MIT. Xem file [LICENSE](LICENSE) để biết thêm chi tiết.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
declare module "@zibot/scdl" {
|
|
2
|
+
import { Readable } from "stream";
|
|
3
|
+
|
|
4
|
+
// Types
|
|
5
|
+
export interface SearchOptions {
|
|
6
|
+
query: string;
|
|
7
|
+
limit?: number;
|
|
8
|
+
offset?: number;
|
|
9
|
+
type?: "all" | "tracks" | "playlists" | "users";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DownloadOptions {
|
|
13
|
+
quality?: "high" | "low";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface Track {
|
|
17
|
+
id: number;
|
|
18
|
+
title: string;
|
|
19
|
+
url: string;
|
|
20
|
+
user: { id: number; username: string };
|
|
21
|
+
media: {
|
|
22
|
+
transcodings: {
|
|
23
|
+
url: string;
|
|
24
|
+
format: { protocol: string; mime_type: string };
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Playlist {
|
|
30
|
+
id: number;
|
|
31
|
+
title: string;
|
|
32
|
+
tracks: Track[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface User {
|
|
36
|
+
id: number;
|
|
37
|
+
username: string;
|
|
38
|
+
followers_count: number;
|
|
39
|
+
track_count: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SearchResponse {
|
|
43
|
+
collection: (Track | Playlist | User)[];
|
|
44
|
+
next_href?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class SoundCloud {
|
|
48
|
+
clientId: string | null;
|
|
49
|
+
apiBaseUrl: string;
|
|
50
|
+
|
|
51
|
+
constructor(options?: { init?: boolean });
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the SoundCloud client to retrieve clientId.
|
|
55
|
+
*/
|
|
56
|
+
init(): Promise<void>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Search for tracks, playlists, or users on SoundCloud.
|
|
60
|
+
*/
|
|
61
|
+
searchTracks(options: SearchOptions): Promise<SearchResponse>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Retrieve detailed information about a single track.
|
|
65
|
+
*/
|
|
66
|
+
getTrack(url: string): Promise<Track>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Retrieve detailed information about a playlist.
|
|
70
|
+
*/
|
|
71
|
+
getPlaylist(url: string): Promise<Playlist>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Download a track as a stream.
|
|
75
|
+
*/
|
|
76
|
+
downloadTrack(url: string, options?: DownloadOptions): Promise<Readable>;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
const m3u8stream = require("m3u8stream");
|
|
3
|
+
|
|
4
|
+
class SoundCloud {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
const defaultOptions = { init: true, apiBaseUrl: "https://api-v2.soundcloud.com" };
|
|
7
|
+
options = { ...defaultOptions, ...options };
|
|
8
|
+
this.clientId = null;
|
|
9
|
+
this.apiBaseUrl = options.apiBaseUrl;
|
|
10
|
+
if (options.init) this.init();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Auto-fetch Client ID
|
|
14
|
+
async init() {
|
|
15
|
+
const clientIdRegex = /client_id=(:?[\w\d]{32})/;
|
|
16
|
+
const soundCloudDom = (await axios.get("https://soundcloud.com")).data;
|
|
17
|
+
const scriptUrls = (soundCloudDom.match(/<script crossorigin src="(.*?)"><\/script>/g) || [])
|
|
18
|
+
.map((tag) => tag.match(/src="(.*?)"/)?.[1])
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
|
|
21
|
+
for (const url of scriptUrls) {
|
|
22
|
+
const response = await axios.get(url);
|
|
23
|
+
const match = response.data.match(clientIdRegex);
|
|
24
|
+
if (match) {
|
|
25
|
+
this.clientId = match[1];
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
throw new Error("Failed to fetch client ID");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Search SoundCloud
|
|
34
|
+
async searchTracks({ query, limit = 20, offset = 0, type = "all" }) {
|
|
35
|
+
const path = type === "all" ? "" : `/${type}`;
|
|
36
|
+
const url = `${this.apiBaseUrl}/search${path}?q=${encodeURIComponent(
|
|
37
|
+
query,
|
|
38
|
+
)}&limit=${limit}&offset=${offset}&access=playable&client_id=${this.clientId}`;
|
|
39
|
+
console.log(url);
|
|
40
|
+
try {
|
|
41
|
+
const { data } = await axios.get(url);
|
|
42
|
+
return data;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("Search error:", error.message || error);
|
|
45
|
+
throw new Error("Search failed");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Get track details
|
|
50
|
+
async getTrackDetails(trackUrl) {
|
|
51
|
+
try {
|
|
52
|
+
return await this.fetchItem(trackUrl);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new Error("Invalid track URL");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Get playlist details
|
|
59
|
+
async getPlaylistDetails(playlistUrl) {
|
|
60
|
+
try {
|
|
61
|
+
const playlist = await this.fetchItem(playlistUrl);
|
|
62
|
+
const { tracks } = playlist;
|
|
63
|
+
|
|
64
|
+
const loadedTracks = tracks.filter((track) => track.title);
|
|
65
|
+
const unloadedTrackIds = tracks.filter((track) => !track.title).map((track) => track.id);
|
|
66
|
+
|
|
67
|
+
if (unloadedTrackIds.length > 0) {
|
|
68
|
+
const moreTracks = await this.fetchTracksByIds(unloadedTrackIds);
|
|
69
|
+
playlist.tracks = loadedTracks.concat(moreTracks);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return playlist;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw new Error("Invalid playlist URL");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Download track stream
|
|
79
|
+
async downloadTrack(trackUrl, options = {}) {
|
|
80
|
+
const track = await this.getTrackDetails(trackUrl);
|
|
81
|
+
const transcoding = track.media.transcodings.find((t) => t.format.protocol === "hls");
|
|
82
|
+
|
|
83
|
+
if (!transcoding) throw new Error("No valid HLS stream found");
|
|
84
|
+
|
|
85
|
+
const m3u8Url = await this.getStreamUrl(transcoding.url);
|
|
86
|
+
return m3u8stream(m3u8Url, options);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Fetch single item (track/playlist/user)
|
|
90
|
+
async fetchItem(itemUrl) {
|
|
91
|
+
const url = `${this.apiBaseUrl}/resolve?url=${itemUrl}&client_id=${this.clientId}`;
|
|
92
|
+
try {
|
|
93
|
+
const { data } = await axios.get(url);
|
|
94
|
+
return data;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new Error("Failed to fetch item details");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fetch multiple tracks by their IDs
|
|
101
|
+
async fetchTracksByIds(trackIds) {
|
|
102
|
+
const ids = trackIds.join(",");
|
|
103
|
+
const url = `${this.apiBaseUrl}/tracks?ids=${ids}&client_id=${this.clientId}`;
|
|
104
|
+
try {
|
|
105
|
+
const { data } = await axios.get(url);
|
|
106
|
+
return data;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new Error("Failed to fetch tracks by IDs");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get HLS stream URL
|
|
113
|
+
async getStreamUrl(transcodingUrl) {
|
|
114
|
+
const url = `${transcodingUrl}?client_id=${this.clientId}`;
|
|
115
|
+
try {
|
|
116
|
+
const { data } = await axios.get(url);
|
|
117
|
+
return data.url;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw new Error("Failed to fetch stream URL");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = SoundCloud;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zibot/scdl",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Soucloud download",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/zijipia/Zibot_Package.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"@zibot/scdl",
|
|
15
|
+
"scdl"
|
|
16
|
+
],
|
|
17
|
+
"author": "Ziji",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/zijipia/Zibot_Package/issues"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/zijipia/Zibot_Package#readme",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"discord.js": "^14.16.3"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"axios": "^1.7.7",
|
|
31
|
+
"events": "^3.3.0",
|
|
32
|
+
"m3u8stream": "^0.8.6"
|
|
33
|
+
}
|
|
34
|
+
}
|