Socket
Socket
Sign inDemoInstall

spottydl

Package Overview
Dependencies
22
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.1.1 to 0.1.2

216

dist/Download.js

@@ -6,3 +6,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadAlbum = exports.downloadTrack = void 0;
exports.retryDownload = exports.downloadAlbum = exports.downloadTrack = void 0;
const index_1 = require("./index");

@@ -14,2 +14,79 @@ const node_id3_1 = __importDefault(require("node-id3"));

const fs_1 = require("fs");
// Private Methods
const dl_track = async (id, filename) => {
return await new Promise((resolve, reject) => {
(0, fluent_ffmpeg_1.default)((0, ytdl_core_1.default)(id, { quality: 'highestaudio', filter: 'audioonly' }))
.audioBitrate(128)
.save(filename)
.on('error', (err) => {
console.error(`Failed to write file (${filename}): ${err}`);
(0, fs_1.unlinkSync)(filename);
resolve(false);
})
.on('end', () => {
resolve(true);
});
});
};
const dl_album_normal = async (obj, oPath, tags) => {
let Results = [];
for await (let res of obj.tracks) {
let filename = `${oPath}${res.name}.mp3`;
let dlt = await dl_track(res.id, filename);
if (dlt) {
let tagStatus = node_id3_1.default.update(tags, filename);
if (tagStatus) {
console.log(`Finished: ${filename}`);
Results.push({ status: 'Success', filename: filename });
}
else {
console.log(`Failed: ${filename} (tags)`);
Results.push({ status: 'Failed (tags)', filename: filename, tags: tags });
}
}
else {
console.log(`Failed: ${filename} (stream)`);
Results.push({ status: 'Failed (stream)', filename: filename, id: res.id, tags: tags });
}
}
return Results;
};
const dl_album_fast = async (obj, oPath, tags) => {
let Results = [];
let i = 0; // Variable for specifying the index of the loop
return await new Promise(async (resolve, reject) => {
for await (let res of obj.tracks) {
let filename = `${oPath}${res.name}.mp3`;
(0, fluent_ffmpeg_1.default)((0, ytdl_core_1.default)(res.id, { quality: 'highestaudio', filter: 'audioonly' }))
.audioBitrate(128)
.save(filename)
.on('error', (err) => {
tags.title = res.name; // Tags
tags.trackNumber = res.trackNumber;
Results.push({ status: 'Failed (stream)', filename: filename, id: res.id, tags: tags });
console.error(`Failed to write file (${filename}): ${err}`);
(0, fs_1.unlinkSync)(filename);
// reject(err)
})
.on('end', () => {
i++;
tags.title = res.name;
tags.trackNumber = res.trackNumber;
let tagStatus = node_id3_1.default.update(tags, filename);
if (tagStatus) {
console.log(`Finished: ${filename}`);
Results.push({ status: 'Success', filename: filename });
}
else {
console.log(`Failed to add tags: ${filename}`);
Results.push({ status: 'Failed (tags)', filename: filename, id: res.id, tags: tags });
}
if (i == obj.tracks.length) {
resolve(Results);
}
});
}
});
};
// END
/**

@@ -19,3 +96,3 @@ * Download the Spotify Track, need a <Track> type for first param, the second param is optional

* @param {string} outputPath - String type, (optional) if not specified the output will be on the current dir
* @returns {Results} <Results> if successful, `string` if failed
* @returns {Results[]} <Results[]> if successful, `string` if failed
*/

@@ -25,4 +102,4 @@ const downloadTrack = async (obj, outputPath = './') => {

// Check type and check if file path exists...
if ((0, index_1.checkType)(obj) != "Track") {
throw Error("obj passed is not of type <Track>");
if ((0, index_1.checkType)(obj) != 'Track') {
throw Error('obj passed is not of type <Track>');
}

@@ -37,25 +114,20 @@ let albCover = await axios_1.default.get(obj.albumCoverURL, { responseType: 'arraybuffer' });

image: {
imageBuffer: Buffer.from(albCover.data, "utf-8")
imageBuffer: Buffer.from(albCover.data, 'utf-8')
}
};
let filename = `${(0, index_1.checkPath)(outputPath)}${obj.title}.mp3`;
let stream = (0, ytdl_core_1.default)(obj.id, { quality: 'highestaudio', filter: 'audioonly' });
return await new Promise((resolve, reject) => {
(0, fluent_ffmpeg_1.default)(stream)
.audioBitrate(128)
.save(filename)
.on('error', (err) => {
console.error(`Failed to write file (${filename}): ${err}`);
reject({ status: false, filename: filename, id: obj.id });
})
.on('end', () => {
let tagStatus = node_id3_1.default.update(tags, filename);
if (tagStatus) {
resolve({ status: true, filename: filename, id: obj.id });
}
else {
reject({ status: false, filename: filename, id: obj.id });
}
});
});
// EXPERIMENTAL
let dlt = await dl_track(obj.id, filename);
if (dlt) {
let tagStatus = node_id3_1.default.update(tags, filename);
if (tagStatus) {
return [{ status: 'Success', filename: filename }];
}
else {
return [{ status: 'Failed (tags)', filename: filename, tags: tags }];
}
}
else {
return [{ status: 'Failed (stream)', filename: filename, id: obj.id, tags: tags }];
}
}

@@ -70,13 +142,13 @@ catch (err) {

* function will return an array of <Results>
* @param {Track} obj An object of type <Album>, contains Album details and info
* @param {Album} obj An object of type <Album>, contains Album details and info
* @param {string} outputPath - String type, (optional) if not specified the output will be on the current dir
* @returns {Results} <Results[]> if successful, `string` if failed
* @param {boolean} sync - Boolean type, (optional) can be `true` or `false`. Default (true) is safer/less errors, for slower bandwidths
* @returns {Results[]} <Results[]> if successful, `string` if failed
*/
const downloadAlbum = async (obj, outputPath = './') => {
const downloadAlbum = async (obj, outputPath = './', sync = true) => {
try {
if ((0, index_1.checkType)(obj) != "Album") {
throw Error("obj passed is not of type <Album>");
if ((0, index_1.checkType)(obj) != 'Album') {
throw Error('obj passed is not of type <Album>');
}
let albCover = await axios_1.default.get(obj.albumCoverURL, { responseType: 'arraybuffer' });
let Results = []; // Use later inside the main loop, then we return this
let tags = {

@@ -87,38 +159,68 @@ artist: obj.artist,

image: {
imageBuffer: Buffer.from(albCover.data, "utf-8")
imageBuffer: Buffer.from(albCover.data, 'utf-8')
}
};
let oPath = (0, index_1.checkPath)(outputPath);
let i = 0; // Variable for specifying the index of the loop
return await new Promise(async (resolve, reject) => {
for await (let res of obj.tracks) {
let filename = `${oPath}${res.name}.mp3`;
let stream = (0, ytdl_core_1.default)(res.id, { quality: 'highestaudio', filter: 'audioonly' });
(0, fluent_ffmpeg_1.default)(stream)
.audioBitrate(128)
.save(filename)
.on('error', (err) => {
Results.push({ status: false, filename: filename, id: res.id });
console.error(`Failed to write file (${filename}): ${err}`);
(0, fs_1.unlinkSync)(filename);
// reject(err)
})
.on('end', () => {
i++;
Results.push({ status: true, filename: filename, id: res.id });
tags.title = res.name;
tags.trackNumber = res.trackNumber;
let tagStatus = node_id3_1.default.update(tags, filename);
console.log(tagStatus ? `Finished: ${filename}` : `Failed to add tags: ${filename}`);
if (i == obj.tracks.length) {
resolve(Results);
if (sync) {
return await dl_album_normal(obj, oPath, tags);
}
else {
return await dl_album_fast(obj, oPath, tags);
}
}
catch (err) {
return `Caught: ${err}`;
}
};
exports.downloadAlbum = downloadAlbum;
/**
* Retries the download process if there are errors. Only use this after `downloadTrack()` or `downloadAlbum()` methods
* checks for failed downloads then tries again, returns <Results[]> object array
* @param {Results[]} obj An object of type <Results[]>, contains an array of results
* @returns {Results[]} <Results[]> array if the download process is successful, `true` if there are no errors and `false` if an error happened.
*/
const retryDownload = async (Info) => {
try {
if ((0, index_1.checkType)(Info) != 'Results[]') {
throw Error('obj passed is not of type <Results[]>');
}
// Filter the results
let failedStream = Info.filter((i) => i.status == 'Failed (stream)' || i.status == 'Failed (tags)');
if (failedStream.length == 0) {
return true;
}
let Results = [];
failedStream.map(async (i) => {
if (i.status == 'Failed (stream)') {
let dlt = await dl_track(i.id, i.filename);
if (dlt) {
let tagStatus = node_id3_1.default.update(i.tags, i.filename);
if (tagStatus) {
Results.push({ status: 'Success', filename: i.filename });
}
});
else {
Results.push({ status: 'Failed (tags)', filename: i.filename, tags: i.tags });
}
}
else {
Results.push({ status: 'Failed (stream)', filename: i.filename, id: i.id, tags: i.tags });
}
}
else if (i.status == 'Failed (tags)') {
let tagStatus = node_id3_1.default.update(i.tags, i.filename);
if (tagStatus) {
Results.push({ status: 'Success', filename: i.filename });
}
else {
Results.push({ status: 'Failed (tags)', filename: i.filename, tags: i.tags });
}
}
});
return Results;
}
catch (err) {
return `Caught: ${err}`;
console.error(`Caught: ${err}`);
return false;
}
};
exports.downloadAlbum = downloadAlbum;
exports.retryDownload = retryDownload;

@@ -6,4 +6,5 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.checkPath = exports.checkType = exports.downloadTrack = exports.downloadAlbum = exports.getTrack = exports.getAlbum = void 0;
exports.checkPath = exports.checkType = exports.retryDownload = exports.downloadTrack = exports.downloadAlbum = exports.getTrack = exports.getAlbum = void 0;
const fs_1 = require("fs");
const util_1 = require("util");
const os_1 = __importDefault(require("os"));

@@ -16,16 +17,20 @@ var Info_1 = require("./Info");

Object.defineProperty(exports, "downloadTrack", { enumerable: true, get: function () { return Download_1.downloadTrack; } });
Object.defineProperty(exports, "retryDownload", { enumerable: true, get: function () { return Download_1.retryDownload; } });
/**
* Check the type of the object, can be of type <Track> or <Album>
* @param {Track|Album} ob An object, can be type <Track> or <Album>
* @returns {string} "Track" | "Album" | "None"
* Check the type of the object, can be of type <Track>, <Album> or <Results[]>
* @param {Track|Album|Results[]} ob An object, can be type <Track>, <Album> or <Results[]>
* @returns {string} "Track" | "Album" | "Results[]" | "None"
*/
const checkType = (ob) => {
if ("title" in ob && "trackNumber" in ob) {
return "Track";
if ('title' in ob && 'trackNumber' in ob) {
return 'Track';
}
else if ("name" in ob && "tracks" in ob) {
return "Album";
else if ('name' in ob && 'tracks' in ob) {
return 'Album';
}
else if ('status' in ob[0] && 'filename' in ob[0] && (0, util_1.isArray)(ob) == true) {
return 'Results[]';
}
else {
return "None";
return 'None';
}

@@ -43,3 +48,3 @@ };

if (!(0, fs_1.existsSync)(c)) {
throw Error("Filepath:( " + c + " ) doesn't exist, please specify absolute path");
throw Error('Filepath:( ' + c + " ) doesn't exist, please specify absolute path");
}

@@ -46,0 +51,0 @@ else if (c.slice(-1) != '/') {

@@ -16,3 +16,3 @@ "use strict";

*/
const getTrack = async (url = "") => {
const getTrack = async (url = '') => {
try {

@@ -33,3 +33,3 @@ // Check if url is a track URL

album: spData.album.name,
id: "ID",
id: 'ID',
albumCoverURL: spData.album.images[0].url,

@@ -39,3 +39,3 @@ trackNumber: spData.track_number

await ytm.initialize();
let trk = await ytm.search(`${tags.title} - ${tags.artist}`, "SONG");
let trk = await ytm.search(`${tags.title} - ${tags.artist}`, 'SONG');
tags.id = trk[0].videoId;

@@ -54,3 +54,3 @@ return tags;

*/
const getAlbum = async (url = "") => {
const getAlbum = async (url = '') => {
try {

@@ -74,5 +74,9 @@ // Check if url is a track URL

await ytm.initialize();
let alb = await ytm.search(`${tags.artist} - ${tags.name}`, "ALBUM");
let alb = await ytm.search(`${tags.artist} - ${tags.name}`, 'ALBUM');
let albData = await ytm.getAlbum(alb[0].albumId);
albData.songs.map((i, n) => tags.tracks.push({ name: i.name, id: i.videoId, trackNumber: n + 1 }));
albData.songs.map((i, n) => tags.tracks.push({
name: spData.tracks.items[n].name,
id: i.videoId,
trackNumber: spData.tracks.items[n].track_number
}));
return tags;

@@ -79,0 +83,0 @@ }

{
"name": "spottydl",
"version": "0.1.1",
"version": "0.1.2",
"description": "NodeJS Spotify Downloader without any API Keys or Authentication",

@@ -11,2 +11,3 @@ "main": "dist/index.js",

"watch": "tsc -w",
"format": "prettier --config .prettierrc 'src/*.ts' --write",
"docs": "typedoc src/index.ts"

@@ -42,2 +43,3 @@ },

"@types/node": "^17.0.8",
"prettier": "^2.5.1",
"typedoc": "^0.22.10",

@@ -44,0 +46,0 @@ "typescript": "^4.5.4"

@@ -11,3 +11,4 @@ # Spottydl

- [x] **Simple and easy to use**, contains only 4 usable methods 🤔 I do need some help optimizing some parts
- [ ] Error checking, when downloading Tracks or Albums, like retrying the process when status failed...
- [x] Error checking, when downloading Tracks or Albums, like retrying the process when status failed...
- [ ] Downloading playlists, as of now Spotify Tracks/Albums are currently supported

@@ -94,8 +95,22 @@ ## Installation

/* Example Output
{
status: true,
filename: '~/somePath/Never Gonna Give You Up.mp3',
id: 'lYBUbBu4W08'
/* Example Output (Successful)
[
{ status: 'Success', filename: '~/somePath/Never Gonna Give You Up.mp3' }
]
*/
/* Example Output (Failed)
[
{
status: 'Failed (stream)',
filename: ~/somePath/Never Gonna Give You Up.mp3,
id: 'lYBUbBu4W08', // videoId from YT-Music
tags: {
title: 'Never Gonna Give You Up',
artist: 'Rick Astley',
year: '1987-11-12',
...
}
}
]
*/

@@ -108,5 +123,5 @@ ```

(async() => {
await SpottyDL.getAlbum("https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT")
await SpottyDL.getAlbum("https://open.spotify.com/album/66MRfhZmuTuyGCO1dJZTRB")
.then(async(results) => {
let album = await SpottyDL.downloadAlbum(results)
let album = await SpottyDL.downloadAlbum(results, "output/", false)
console.log(album)

@@ -116,10 +131,20 @@ });

/* Example Output
/* Example Output (Successful)
[
{
status: true,
filename: "./'K.'.mp3",
id: 'L4sbDxR22z4'
{ status: 'Success', filename: 'output/Crush.mp3' },
{ status: 'Success', filename: 'output/Sesame Syrup.mp3' }
]
*/
/* Example Output (Failed) some tracks failed proceed to use `retryDownload()` method
[
{
status: 'Failed (Stream)',
filename: 'output/Crush.mp3',
id: YT-Music id,
tags: {
// tags for the track...
}
},
{...}
{ status: 'Success', filename: 'output/Sesame Syrup.mp3' }
]

@@ -129,2 +154,29 @@ */

#### Retrying a failed download (Album/Track)
```JS
(async() => {
await SpottyDL.getAlbum("https://open.spotify.com/album/66MRfhZmuTuyGCO1dJZTRB")
.then(async(results) => {
let album = await SpottyDL.downloadAlbum(results, "output/", false)
let res = await SpottyDL.retryDownload(album);
console.log(res) // boolean or <Results[]>
});
})();
// Using a while loop until all tracks have no errors (Experimental)
(async() => {
await SpottyDL.getAlbum("https://open.spotify.com/album/66MRfhZmuTuyGCO1dJZTRB")
.then(async(results) => {
let album = await SpottyDL.downloadAlbum(results, "output/", false)
let res = await SpottyDL.retryDownload(album);
while(res != true) {
res = await SpottyDL.retryDownload(res);
console.log(res) // boolean or <Results[]>
}
});
})();
```
## Notes

@@ -131,0 +183,0 @@

@@ -6,12 +6,20 @@ import { Album, Track, Results } from './index';

* @param {string} outputPath - String type, (optional) if not specified the output will be on the current dir
* @returns {Results} <Results> if successful, `string` if failed
* @returns {Results[]} <Results[]> if successful, `string` if failed
*/
export declare const downloadTrack: (obj: Track, outputPath?: string) => Promise<Results | string>;
export declare const downloadTrack: (obj: Track, outputPath?: string) => Promise<Results[] | string>;
/**
* Download the Spotify Album, need a <Album> type for first param, the second param is optional,
* function will return an array of <Results>
* @param {Track} obj An object of type <Album>, contains Album details and info
* @param {Album} obj An object of type <Album>, contains Album details and info
* @param {string} outputPath - String type, (optional) if not specified the output will be on the current dir
* @returns {Results} <Results[]> if successful, `string` if failed
* @param {boolean} sync - Boolean type, (optional) can be `true` or `false`. Default (true) is safer/less errors, for slower bandwidths
* @returns {Results[]} <Results[]> if successful, `string` if failed
*/
export declare const downloadAlbum: (obj: Album, outputPath?: string) => Promise<Results[] | string>;
export declare const downloadAlbum: (obj: Album, outputPath?: string, sync?: boolean) => Promise<Results[] | string>;
/**
* Retries the download process if there are errors. Only use this after `downloadTrack()` or `downloadAlbum()` methods
* checks for failed downloads then tries again, returns <Results[]> object array
* @param {Results[]} obj An object of type <Results[]>, contains an array of results
* @returns {Results[]} <Results[]> array if the download process is successful, `true` if there are no errors and `false` if an error happened.
*/
export declare const retryDownload: (Info: Results[]) => Promise<Results[] | boolean>;
export { getAlbum, getTrack } from './Info';
export { downloadAlbum, downloadTrack } from './Download';
export { downloadAlbum, downloadTrack, retryDownload } from './Download';
export declare type Track = {

@@ -19,13 +19,14 @@ title: string;

};
export declare type Results = {
status: boolean;
export interface Results {
status: 'Success' | 'Failed (stream)' | 'Failed (tags)';
filename: string;
id: string;
};
id?: string;
tags?: object;
}
/**
* Check the type of the object, can be of type <Track> or <Album>
* @param {Track|Album} ob An object, can be type <Track> or <Album>
* @returns {string} "Track" | "Album" | "None"
* Check the type of the object, can be of type <Track>, <Album> or <Results[]>
* @param {Track|Album|Results[]} ob An object, can be type <Track>, <Album> or <Results[]>
* @returns {string} "Track" | "Album" | "Results[]" | "None"
*/
export declare const checkType: (ob: Track | Album) => "Track" | "Album" | "None";
export declare const checkType: (ob: Track | Album | Results[]) => 'Track' | 'Album' | 'Results[]' | 'None';
/**

@@ -32,0 +33,0 @@ * Check the path if it exists, if not then we throw an error

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc