Skip to content

Commit

Permalink
Merge pull request #6 from mitsuki31/chore/configure-eslint
Browse files Browse the repository at this point in the history
Configure ESLint and ESLint CI 🤖
  • Loading branch information
mitsuki31 authored Nov 19, 2023
2 parents cde3a69 + 7cd0a87 commit fa9faa7
Show file tree
Hide file tree
Showing 6 changed files with 1,286 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Ignore installed dependencies
node_modules/

# ESLint configuration file
.eslintrc*
65 changes: 65 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module.exports = {
env: {
node: true,
es6: true
},
extends: 'eslint:recommended',
root: true,
parserOptions: {
ecmaVersion: 'latest',
project: true
},
rules: {
strict: ['error', 'safe'],
'eol-last': ['error', 'always'],
eqeqeq: ['error', 'always'],
'prefer-const': ['error'],
'max-len': [
'warn',
{
code: 90,
ignoreComments: true,
ignoreTrailingComments: true,
ignoreUrls: true,
ignoreStrings: true,
ignoreRegExpLiterals: true
}
],
indent: [
'error',
4,
{
SwitchCase: 1,
VariableDeclarator: 'first',
FunctionExpression: {
parameters: 'first',
body: 1
}
}
],
'linebreak-style': [
'warn',
'unix'
],
quotes: [
'warn',
'single',
{ avoidEscape: true }
],
semi: [
'error',
'always'
],
camelcase: [
'error',
{ properties: 'always' }
],
curly: [
'error',
'multi-line',
'consistent'
],
'no-else-return': ['error'],
'default-param-last': ['error'],
}
};
29 changes: 29 additions & 0 deletions .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Lint

on:
push:
pull_request:
branches: [ "master" ]

jobs:
build:
name: Ubuntu / ${{ matrix.node-ver }}
runs-on: ubuntu-latest
env:
NODE_ENV: development
strategy:
matrix:
node-ver: [16.x, 18.x, 20.x, latest]

steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js / v${{ matrix.node-ver }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-ver }}
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Lint the project
run: npm run lint
107 changes: 66 additions & 41 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,32 +53,32 @@ function normalizeYtMusicUrl(url) {
}

// Trim to the right if the URL has '&si=' (YT Music only)
if (/^http(s?)?:\/\/.+\?v=.+&si=?/) {
if (/^http(s?)?:\/\/.+\?v=.+&si=?/.test(url)) {
url = url.replace(/&si=.+/, '');
}

return url;
}

function getVideosInfo(...urls) {
return new Promise(async (resolve, reject) => {
let videosInfo = [];

const promises = urls.map((url) => {
try {
await Promise.all(urls.map(async (url) => {
ytdl.validateURL(url); // Validate the URL
videosInfo.push(await ytdl.getInfo(url)); // Get the video info
}));
ytdl.validateURL(url); // Validate the URL
return ytdl.getInfo(url); // Return the promise for video info retrieval
} catch (error) {
reject(error);
// Reject the promise if validation or video info retrieval fails
return Promise.reject(error);
}
// Return the list representing the videos information
resolve(videosInfo);
});

return Promise.all(promises)
.then((videosInfo) => videosInfo)
.catch((error) => Promise.reject(error));
}

function createDownloadProgress(chunk, bytesDownloaded, totalBytes) {
const percentage = Math.max(0, Math.floor(bytesDownloaded / totalBytes * 100));
const percentage = Math.max(0,
Math.floor(bytesDownloaded / totalBytes * 100));
process.stdout.write(`[INFO] Download progress: ${percentage}%\r`);
}

Expand All @@ -91,7 +91,7 @@ function argumentParser(args) {
console.log("'downloads.txt' are found.");
} else {
console.error(
new Error(`[ERROR] No argument specified and 'downloads.txt' not exist`)
new Error('[ERROR] No argument specified and \'downloads.txt\' not exist')
);
console.error('[ERROR] ytmp3 exited with code 1');
process.exit(1);
Expand All @@ -106,7 +106,7 @@ function argumentParser(args) {
function singleDownload(inputUrl) {
console.log(`[INFO] Input URL: ${inputUrl}`);

const illegalCharRegex = /[<>:"\/\\|?*\x00-\x1F]/g;
const illegalCharRegex = /[<>:"/\\|?*]/g;

// Validate the given URL
ytdl.validateURL(normalizeYtMusicUrl(inputUrl));
Expand Down Expand Up @@ -134,19 +134,23 @@ function singleDownload(inputUrl) {
outStream = fs.createWriteStream(outFile);

const dlErrorLog = path.resolve(
__dirname, 'tmp', `dlError-${(new Date()).toISOString().split('.')[0]}.log`);
__dirname, 'tmp', `dlError-${(new Date()).toISOString().split('.')[0]}.log`
);

// Create the output directory asynchronously, if not exist
if (!fs.existsSync(path.dirname(outFile))) {
fs.mkdirSync(path.dirname(outFile), { recursive: true });
}

console.log(`[INFO] Downloading '${parsedData.title}'...`);
console.log(' ', {
console.log({
author: parsedData.author,
videoUrl: parsedData.videoUrl,
viewers: parseInt(parsedData.viewers, 10).toLocaleString('en')
});
ytdl.downloadFromInfo(parsedData.videoInfo, { format: parsedData.format })
ytdl.downloadFromInfo(parsedData.videoInfo, {
format: parsedData.format
})
.on('progress', createDownloadProgress)
.on('end', () => {
console.log(`[DONE] Download finished: ${outFileBase}`);
Expand All @@ -160,34 +164,39 @@ function singleDownload(inputUrl) {
dlErrorLog, { flags: 'a+', flush: true }
);

dlErrorLogStream.write(`[ERROR] ${err.message}${os.EOL}`);
dlErrorLogStream.write(` Title: ${parsedData.title}${os.EOL}`);
dlErrorLogStream.write(` Author: ${parsedData.author}${os.EOL}`);
dlErrorLogStream.write(` URL: ${parsedData.videoUrl}${os.EOL}`);
dlErrorLogStream.write(
`[ERROR] ${err.message}${os.EOL}`);
dlErrorLogStream.write(
` Title: ${parsedData.title}${os.EOL}`);
dlErrorLogStream.write(
` Author: ${parsedData.author}${os.EOL}`);
dlErrorLogStream.write(
` URL: ${parsedData.videoUrl}${os.EOL}`);
console.error(err);
})
.pipe(outStream);

outStream
.on('finish', () => {
console.log(`[DONE] Written successfully.\n`);
console.log('[DONE] Written successfully.\n');
convertToMp3(outFile); // Convert to MP3
})
.on('error', (err) => {
console.error(`[ERROR] Unable to write to output file: ${outFile}\n`);
console.error(
`[ERROR] Unable to write to output file: ${outFile}\n`);
console.error(err);
process.exit(1);
});
})
.catch((err) => console.error(err));
.catch((err) => console.error(err));
}

function batchDownload(inputFile) {
const urlsFile = path.resolve(inputFile);
console.log(`[INFO] Input File: ${path.basename(urlsFile)}`);

// All illegal characters for file names
const illegalCharRegex = /[<>:"\/\\|?*\x00-\x1F]/g;
const illegalCharRegex = /[<>:"/\\|?*]/g;

fs.promises.readFile(urlsFile, 'utf8')
.then((contents) => {
Expand All @@ -206,10 +215,10 @@ function batchDownload(inputFile) {
console.warn('[WARNING] Maximum batch download cannot exceed than 15 URLs!');
}
const urls = contents.map((url) => normalizeYtMusicUrl(url))
.slice(0, 15); // Maximum batch: 15 URLs
.slice(0, 15); // Maximum batch: 15 URLs

return new Promise((resolve, reject) => {
let downloadFiles = []; // Store all downloaded files
const downloadFiles = []; // Store all downloaded files
getVideosInfo(...urls)
.then((data) => {
const parsedData = [];
Expand All @@ -221,7 +230,8 @@ function batchDownload(inputFile) {
quality: 140,
filter: 'audioonly'
}),
title: videoInfo.videoDetails.title.replace(illegalCharRegex, '_'),
title: videoInfo.videoDetails.title.replace(
illegalCharRegex, '_'),
author: videoInfo.videoDetails.author.name,
videoUrl: videoInfo.videoDetails.video_url,
videoId: videoInfo.videoDetails.videoId,
Expand All @@ -243,50 +253,65 @@ function batchDownload(inputFile) {
// Create output path and the write stream
const outFile = path.join('download', `${data.title}.m4a`),
outFileBase = path.basename(outFile),
outStream = fs.createWriteStream(outFile, { autoClose: true });
outStream = fs.createWriteStream(outFile);

// Create the output directory asynchronously, if not exist
if (!fs.existsSync(path.dirname(outFile))) {
fs.mkdirSync(path.dirname(outFile), { recursive: true });
fs.mkdirSync(path.dirname(outFile), {
recursive: true
});
}

console.log(`[INFO] Downloading '${data.title}'...`);
console.log(' ', {
console.log({
author: data.author,
videoUrl: data.videoUrl,
viewers: parseInt(data.viewers, 10).toLocaleString('en')
});
ytdl.downloadFromInfo(videoInfo, { format: format })
.on('progress', createDownloadProgress)
.on('end', () => {
console.log(`[DONE] Download finished: ${outFileBase}`);
console.log(
`[DONE] Download finished: ${outFileBase}`);
})
.on('error', (err) => {
console.error(`[ERROR] Download error: ${outFileBase}`);
console.error(
`[ERROR] Download error: ${outFileBase}`);
if (!fs.existsSync(path.dirname(dlErrorLog))) {
fs.mkdirSync(path.dirname(dlErrorLog, { recursive: true }));
fs.mkdirSync(path.dirname(dlErrorLog, {
recursive: true
}));
}
const dlErrorLogStream = fs.createWriteStream(
dlErrorLog, { flags: 'a+', flush: true }
);

dlErrorLogStream.write(`[ERROR] ${err.message}${os.EOL}`);
dlErrorLogStream.write(` Title: ${data.title}${os.EOL}`);
dlErrorLogStream.write(` Author: ${data.author}${os.EOL}`);
dlErrorLogStream.write(` URL: ${data.videoUrl}${os.EOL}`);
dlErrorLogStream.write(
`[ERROR] ${err.message}${os.EOL}`);
dlErrorLogStream.write(
` Title: ${data.title}${os.EOL}`);
dlErrorLogStream.write(
` Author: ${data.author}${os.EOL}`);
dlErrorLogStream.write(
` URL: ${data.videoUrl}${os.EOL}`);
reject(err);
})
.pipe(outStream);

outStream
.on('finish', () => {
console.log(`[DONE] Written successfully.\n`);
console.log('[DONE] Written successfully.\n');
downloadFiles.push(outFile);

// Return all downloaded audio files
if (downloadFiles.length === urls.length) resolve(downloadFiles);
if (downloadFiles.length === urls.length) {
resolve(downloadFiles);
}
})
.on('error', (err) => {
console.error(`[ERROR] Unable to write to output file: ${outFile}\n`);
console.error(
`[ERROR] Unable to write to output file: ${
outFile}\n`);
reject(err);
});
});
Expand Down
Loading

0 comments on commit fa9faa7

Please sign in to comment.