@nasheedstation/soundcloud-downloader
Version:
Download Soundcloud audio with Node.js, this is a forked version, the original script was written by @zackradisic
97 lines (79 loc) • 4.6 kB
text/typescript
/** @internal @packageDocumentation */
import { AxiosInstance } from 'axios'
import m3u8stream from 'm3u8stream'
import { handleRequestErrs, appendURL } from './util'
import getInfo, { Transcoding } from './info'
export const getMediaURL = async (url: string, clientID: string, axiosInstance: AxiosInstance): Promise<string> => {
const res = await axiosInstance.get(appendURL(url, 'client_id', clientID), {
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36',
Accept: '*/*',
'Accept-Encoding': 'gzip, deflate, br'
},
withCredentials: true
})
if (!res.data.url) throw new Error(`Invalid response from Soundcloud. Check if the URL provided is correct: ${url}`)
return res.data.url
}
export const getProgressiveStream = async (mediaUrl: string, axiosInstance: AxiosInstance): Promise<NodeJS.ReadableStream> => {
const r = await axiosInstance.get(mediaUrl, {
withCredentials: true,
responseType: 'stream'
})
return r.data
}
export const getHLSStream = (mediaUrl: string): m3u8stream.Stream => m3u8stream(mediaUrl)
type fromURLFunctionBase = (url: string, clientID: string,
getMediaURLFunction: (url: string, clientID: string, axiosInstance: AxiosInstance) => Promise<string>,
getProgressiveStreamFunction: (mediaUrl: string, axiosInstance: AxiosInstance) => Promise<NodeJS.ReadableStream>,
getHLSStreamFunction: (mediaUrl: string) => m3u8stream.Stream,
axiosInstance: AxiosInstance) => Promise<NodeJS.ReadableStream | m3u8stream.Stream>
export const fromURLBase: fromURLFunctionBase = async (url: string, clientID: string,
getMediaURLFunction: (url: string, clientID: string, axiosInstance: AxiosInstance) => Promise<string>,
getProgressiveStreamFunction: (mediaUrl: string, axiosInstance: AxiosInstance) => Promise<NodeJS.ReadableStream>,
getHLSStreamFunction: (mediaUrl: string) => m3u8stream.Stream,
axiosInstance: AxiosInstance): Promise<NodeJS.ReadableStream | m3u8stream.Stream> => {
try {
const mediaUrl = await getMediaURLFunction(url, clientID, axiosInstance)
if (url.includes('/progressive')) {
return await getProgressiveStreamFunction(mediaUrl, axiosInstance)
}
return getHLSStreamFunction(mediaUrl)
} catch (err) {
throw handleRequestErrs(err)
}
}
export const fromURL = async (url: string, clientID: string, axiosInstance: AxiosInstance): Promise<NodeJS.ReadableStream | m3u8stream.Stream> => await fromURLBase(url, clientID, getMediaURL, getProgressiveStream, getHLSStream, axiosInstance)
export const fromMediaObjBase = async (media: Transcoding, clientID: string,
getMediaURLFunction: (url: string, clientID: string, axiosInstance: AxiosInstance) => Promise<string>,
getProgressiveStreamFunction: (mediaUrl: string, axiosInstance: AxiosInstance) => Promise<NodeJS.ReadableStream>,
getHLSStreamFunction: (mediaUrl: string) => m3u8stream.Stream,
fromURLFunction: typeof fromURL,
axiosInstance: AxiosInstance): Promise<NodeJS.ReadableStream | m3u8stream.Stream> => {
if (!validatemedia(media)) throw new Error('Invalid media object provided')
return await fromURLFunction(media.url, clientID, axiosInstance)
}
export const fromMediaObj = async (media: Transcoding, clientID: string, axiosInstance: AxiosInstance): Promise<NodeJS.ReadableStream | m3u8stream.Stream> => await fromMediaObjBase(media, clientID, getMediaURL, getProgressiveStream, getHLSStream, fromURL, axiosInstance)
export const fromDownloadLink = async (id: number, clientID: string, axiosInstance: AxiosInstance): Promise<NodeJS.ReadableStream> => {
const { data: { redirectUri } } = await axiosInstance.get(appendURL(`https://api-v2.soundcloud.com/tracks/${id}/download`, 'client_id', clientID))
const { data } = await axiosInstance.get(redirectUri, {
responseType: 'stream'
})
return data
}
/** @internal */
export const download = async (url: string, clientID: string, axiosInstance: AxiosInstance, useDownloadLink = true): Promise<NodeJS.ReadableStream | m3u8stream.Stream> => {
const info = await getInfo(url, clientID, axiosInstance)
if (info.downloadable && useDownloadLink) {
// Some tracks have `downloadable` set to true but will return a 404
// when using download API route.
try {
return await fromDownloadLink(info.id, clientID, axiosInstance)
} catch (err) {
}
}
return await fromMediaObj(info.media.transcodings[0], clientID, axiosInstance)
}
const validatemedia = (media: Transcoding) => {
return !(!media.url || !media.format)
}