soundcloud.ts
Advanced tools
Comparing version 0.1.5 to 0.1.6
@@ -16,2 +16,6 @@ /// <reference types="node" /> | ||
awaitStream: (writeStream: stream.Writable) => Promise<unknown>; | ||
/** | ||
* Downloads the stream of a track. Note: Requires ffmpeg because of the dependency on audioconcat. | ||
*/ | ||
downloadTrackStream: (songUrl: string, title: string, folder: string) => Promise<string>; | ||
downloadTrack: (trackResolvable: string | number | SoundCloudTrack, folder?: string) => Promise<string>; | ||
@@ -23,2 +27,3 @@ downloadTracks: (tracks: SoundCloudTrack[], dest?: string, limit?: number) => Promise<string[]>; | ||
streamTrack: (trackResolvable: string | number | SoundCloudTrack, folder?: string) => Promise<fs.ReadStream>; | ||
private removeDirectory; | ||
} |
@@ -61,4 +61,70 @@ "use strict"; | ||
}); }; | ||
/** | ||
* Downloads the stream of a track. Note: Requires ffmpeg because of the dependency on audioconcat. | ||
*/ | ||
this.downloadTrackStream = function (songUrl, title, folder) { return __awaiter(_this, void 0, void 0, function () { | ||
var headers, html, match, url, result, streamUrls, src, chunkList, i, chunkPath, dest, res, audioconcat, finalMP3; | ||
var _a; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
if (title.endsWith(".mp3")) | ||
title = title.replace(".mp3", ""); | ||
headers = { | ||
"referer": "soundcloud.com", | ||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" | ||
}; | ||
return [4 /*yield*/, axios_1["default"].get(songUrl, { headers: headers })]; | ||
case 1: | ||
html = _b.sent(); | ||
match = (_a = html.data.match(/(?<="transcodings":\[{"url":")(.*?)(?=")/)) === null || _a === void 0 ? void 0 : _a[0]; | ||
if (!match) return [3 /*break*/, 3]; | ||
return [4 /*yield*/, axios_1["default"].get(match + ("?client_id=" + this.api.clientID), { headers: headers }).then(function (r) { return r.data.url; })]; | ||
case 2: | ||
url = _b.sent(); | ||
return [3 /*break*/, 4]; | ||
case 3: return [2 /*return*/, null]; | ||
case 4: return [4 /*yield*/, axios_1["default"].get(url, { headers: headers }).then(function (d) { return d.data; })]; | ||
case 5: | ||
result = _b.sent(); | ||
streamUrls = result.match(/(https:\/\/cf-hls-media.sndcdn.com)((.|\n)*?)(?=#)/gm); | ||
if (!fs.existsSync(folder)) | ||
fs.mkdirSync(folder); | ||
src = path.join(folder, "concat"); | ||
if (!fs.existsSync(src)) | ||
fs.mkdirSync(src); | ||
chunkList = []; | ||
i = 0; | ||
_b.label = 6; | ||
case 6: | ||
if (!(i < streamUrls.length)) return [3 /*break*/, 9]; | ||
chunkPath = "" + title + i + ".mp3"; | ||
dest = path.join(src, chunkPath); | ||
return [4 /*yield*/, axios_1["default"].get(streamUrls[i], { responseType: "arraybuffer" })]; | ||
case 7: | ||
res = _b.sent(); | ||
fs.writeFileSync(dest, Buffer.from(res.data, "binary")); | ||
chunkList.push(dest); | ||
_b.label = 8; | ||
case 8: | ||
i++; | ||
return [3 /*break*/, 6]; | ||
case 9: | ||
audioconcat = require("audioconcat"); | ||
finalMP3 = path.join(folder, title + ".mp3"); | ||
return [4 /*yield*/, new Promise(function (resolve) { | ||
audioconcat(chunkList).concat(finalMP3) | ||
.on("end", function () { | ||
resolve(); | ||
}); | ||
})]; | ||
case 10: | ||
_b.sent(); | ||
this.removeDirectory(src); | ||
return [2 /*return*/, finalMP3]; | ||
} | ||
}); | ||
}); }; | ||
this.downloadTrack = function (trackResolvable, folder) { return __awaiter(_this, void 0, void 0, function () { | ||
var track, result, dest, result, dest; | ||
var track, result, dest; | ||
return __generator(this, function (_a) { | ||
@@ -80,3 +146,3 @@ switch (_a.label) { | ||
if (!(track.downloadable === true)) return [3 /*break*/, 5]; | ||
return [4 /*yield*/, axios_1["default"].get(track.download_url, { responseType: "arraybuffer", params: { client_id: this.api.clientID, oauth_token: this.api.oauthToken } })]; | ||
return [4 /*yield*/, axios_1["default"].get(track.download_url, { responseType: "arraybuffer", params: { client_id: this.api.clientID } })]; | ||
case 4: | ||
@@ -87,8 +153,3 @@ result = _a.sent(); | ||
return [2 /*return*/, dest]; | ||
case 5: return [4 /*yield*/, axios_1["default"].get(track.stream_url, { responseType: "arraybuffer", params: { client_id: this.api.clientID, oauth_token: this.api.oauthToken } })]; | ||
case 6: | ||
result = _a.sent(); | ||
dest = path.join(folder, track.title + ".mp3"); | ||
fs.writeFileSync(dest, Buffer.from(result.data, "binary")); | ||
return [2 /*return*/, dest]; | ||
case 5: return [2 /*return*/, this.downloadTrackStream(track.permalink_url, track.title, folder)]; | ||
} | ||
@@ -194,4 +255,26 @@ }); | ||
} | ||
// Remove directory recursively | ||
Util.prototype.removeDirectory = function (dir) { | ||
if (dir === "/" || dir === "./") | ||
return; | ||
if (fs.existsSync(dir)) { | ||
fs.readdirSync(dir).forEach(function (entry) { | ||
var entryPath = path.join(dir, entry); | ||
if (fs.lstatSync(entryPath).isDirectory()) { | ||
this.removeDirectory(entryPath); | ||
} | ||
else { | ||
fs.unlinkSync(entryPath); | ||
} | ||
}); | ||
try { | ||
fs.rmdirSync(dir); | ||
} | ||
catch (e) { | ||
console.log(e); | ||
} | ||
} | ||
}; | ||
return Util; | ||
}()); | ||
exports.Util = Util; |
@@ -41,3 +41,3 @@ "use strict"; | ||
require("dotenv").config(); | ||
var soundcloud = new soundcloud_1["default"](process.env.SOUNDCLOUD_CLIENT_ID, process.env.SOUNDCLOUD_OAUTH_TOKEN); | ||
var soundcloud = new soundcloud_1["default"](process.env.SOUNDCLOUD_CLIENT_ID); | ||
(function () { return __awaiter(void 0, void 0, void 0, function () { | ||
@@ -47,14 +47,5 @@ var result; | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, soundcloud.util.downloadTrack("https://soundcloud.com/dwshin/kumiho", "./tracks") | ||
// await soundcloud.util.downloadPlaylist("https://soundcloud.com/tenpimusic/sets/my-songs", "./tracks") | ||
// await soundcloud.util.downloadSearch("virtual riot") | ||
// await soundcloud.util.downloadSearch("virtual riot", "./tracks", 10) | ||
// const result = await soundcloud.tracks.search({q: "anime"}) | ||
]; | ||
case 0: return [4 /*yield*/, soundcloud.util.downloadTrack("https://soundcloud.com/mameyudoufu/vintage-computers", "./tracks")]; | ||
case 1: | ||
result = _a.sent(); | ||
// await soundcloud.util.downloadPlaylist("https://soundcloud.com/tenpimusic/sets/my-songs", "./tracks") | ||
// await soundcloud.util.downloadSearch("virtual riot") | ||
// await soundcloud.util.downloadSearch("virtual riot", "./tracks", 10) | ||
// const result = await soundcloud.tracks.search({q: "anime"}) | ||
console.log(result); | ||
@@ -61,0 +52,0 @@ return [2 /*return*/]; |
@@ -25,2 +25,44 @@ import axios from "axios" | ||
/** | ||
* Downloads the stream of a track. Note: Requires ffmpeg because of the dependency on audioconcat. | ||
*/ | ||
public downloadTrackStream = async (songUrl: string, title: string, folder: string) => { | ||
if (title.endsWith(".mp3")) title = title.replace(".mp3", "") | ||
const headers = { | ||
"referer": "soundcloud.com", | ||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" | ||
} | ||
const html = await axios.get(songUrl, {headers}) | ||
const match = html.data.match(/(?<="transcodings":\[{"url":")(.*?)(?=")/)?.[0] | ||
let url: string | ||
if (match) { | ||
url = await axios.get(match + `?client_id=${this.api.clientID}`, {headers}).then((r) => r.data.url) | ||
} else { | ||
return null | ||
} | ||
const result = await axios.get(url, {headers}).then((d) => d.data) | ||
const streamUrls = result.match(/(https:\/\/cf-hls-media.sndcdn.com)((.|\n)*?)(?=#)/gm) | ||
if (!fs.existsSync(folder)) fs.mkdirSync(folder) | ||
const src = path.join(folder, "concat") | ||
if (!fs.existsSync(src)) fs.mkdirSync(src) | ||
const chunkList: string[] = [] | ||
for (let i = 0; i < streamUrls.length; i++) { | ||
const chunkPath = `${title}${i}.mp3` | ||
const dest = path.join(src, chunkPath) | ||
const res = await axios.get(streamUrls[i], {responseType: "arraybuffer"}) | ||
fs.writeFileSync(dest, Buffer.from(res.data, "binary")) | ||
chunkList.push(dest) | ||
} | ||
const audioconcat = require("audioconcat") | ||
const finalMP3 = path.join(folder, `${title}.mp3`) | ||
await new Promise((resolve) => { | ||
audioconcat(chunkList).concat(finalMP3) | ||
.on("end", () => { | ||
resolve() | ||
}) | ||
}) | ||
this.removeDirectory(src) | ||
return finalMP3 | ||
} | ||
public downloadTrack = async (trackResolvable: string | number | SoundCloudTrack, folder?: string) => { | ||
@@ -36,3 +78,3 @@ let track: SoundCloudTrack | ||
if (track.downloadable === true) { | ||
const result = await axios.get(track.download_url, {responseType: "arraybuffer", params: {client_id: this.api.clientID, oauth_token: this.api.oauthToken}}) | ||
const result = await axios.get(track.download_url, {responseType: "arraybuffer", params: {client_id: this.api.clientID}}) | ||
const dest = path.join(folder, `${track.title}.${result.headers["x-amz-meta-file-type"]}`) | ||
@@ -42,6 +84,3 @@ fs.writeFileSync(dest, Buffer.from(result.data, "binary")) | ||
} else { | ||
const result = await axios.get(track.stream_url, {responseType: "arraybuffer", params: {client_id: this.api.clientID, oauth_token: this.api.oauthToken}}) | ||
const dest = path.join(folder, `${track.title}.mp3`) | ||
fs.writeFileSync(dest, Buffer.from(result.data, "binary")) | ||
return dest | ||
return this.downloadTrackStream(track.permalink_url, track.title, folder) | ||
} | ||
@@ -100,2 +139,22 @@ } | ||
} | ||
// Remove directory recursively | ||
private removeDirectory(dir: string) { | ||
if (dir === "/" || dir === "./") return | ||
if (fs.existsSync(dir)) { | ||
fs.readdirSync(dir).forEach(function(entry) { | ||
const entryPath = path.join(dir, entry) | ||
if (fs.lstatSync(entryPath).isDirectory()) { | ||
this.removeDirectory(entryPath) | ||
} else { | ||
fs.unlinkSync(entryPath) | ||
} | ||
}) | ||
try { | ||
fs.rmdirSync(dir) | ||
} catch (e) { | ||
console.log(e) | ||
} | ||
} | ||
} | ||
} |
{ | ||
"name": "soundcloud.ts", | ||
"version": "0.1.5", | ||
"version": "0.1.6", | ||
"description": "Wrapper for the Soundcloud API with typings", | ||
@@ -36,5 +36,6 @@ "main": "dist/soundcloud.js", | ||
"typedoc": "^0.15.0", | ||
"typescript": "^3.6.4" | ||
"typescript": "^3.8.3" | ||
}, | ||
"dependencies": { | ||
"audioconcat": "^0.1.3", | ||
"axios": "^0.19.0", | ||
@@ -41,0 +42,0 @@ "request": "^2.88.0" |
import SoundCloud from "./soundcloud" | ||
require("dotenv").config() | ||
const soundcloud = new SoundCloud(process.env.SOUNDCLOUD_CLIENT_ID, process.env.SOUNDCLOUD_OAUTH_TOKEN); | ||
const soundcloud = new SoundCloud(process.env.SOUNDCLOUD_CLIENT_ID); | ||
(async () => { | ||
// const result = await soundcloud.tracks.search({q: "virtual riot"}) | ||
const result = await soundcloud.util.downloadTrack("https://soundcloud.com/dwshin/kumiho", "./tracks") | ||
// await soundcloud.util.downloadPlaylist("https://soundcloud.com/tenpimusic/sets/my-songs", "./tracks") | ||
@@ -12,3 +11,4 @@ // await soundcloud.util.downloadSearch("virtual riot") | ||
// const result = await soundcloud.tracks.search({q: "anime"}) | ||
const result = await soundcloud.util.downloadTrack("https://soundcloud.com/mameyudoufu/vintage-computers", "./tracks") | ||
console.log(result) | ||
})() |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
192041
4013
4
3
+ Addedaudioconcat@^0.1.3
+ Addedasync@0.2.10(transitive)
+ Addedaudioconcat@0.1.4(transitive)
+ Addedfluent-ffmpeg@2.1.3(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedwhich@1.3.1(transitive)