diff --git a/cli/bin/index.js b/cli/bin/index.js index f6d4b66..b2ab859 100755 --- a/cli/bin/index.js +++ b/cli/bin/index.js @@ -11,7 +11,7 @@ import { handleExitError } from '../lib/handlers.js'; process.on('unhandledRejection', (err) => handleExitError(err, 'Unhandled rejection')); process.on('uncaughtException', (err) => handleExitError(err, 'Uncaught exception')); -program.name('coryd').description('🪄 Run commands, download things and have fun.').version('1.0.0'); +program.name('coryd').description('🪄 Run commands, download things and have fun.').version('1.1.0'); program.command('init').description('Initialize CLI and populate required config.').action(initConfig); program.command('run [script]').description('Run site scripts and commands.').action(runRootScript); program.command('download').description('Download, name and store image assets.').action(downloadAsset); diff --git a/cli/lib/download.js b/cli/lib/download.js index 71192c1..648b6a2 100644 --- a/cli/lib/download.js +++ b/cli/lib/download.js @@ -17,36 +17,69 @@ const downloadImage = (url, dest) => new Promise((resolve, reject) => { file.on('finish', () => file.close(resolve)); }).on('error', reject); }); -const isValidTMDBUrl = (val) => - /^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || '❌ Must be a valid TMDB image url'; +const isValidTMDBUrl = (val) => /^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || '❌ Must be a valid TMDB image url'; +const overwriteImageDownloadPrompt = async (url, finalPath, fileName) => { + await fs.ensureDir(path.dirname(finalPath)); + + if (await fs.pathExists(finalPath)) { + const { overwrite } = await inquirer.prompt({ + type: 'confirm', + name: 'overwrite', + message: `${fileName} already exists. Overwrite?`, + default: false + }); + + if (!overwrite) { + console.log(`⚠️ Skipped existing file: ${fileName}`); + return; + } + } + + await downloadImage(url, finalPath); + console.log(`✅ Saved to: ${finalPath}`); +}; export const downloadWatchingImages = async () => { const config = await loadConfig(); - const { mediaType } = await inquirer.prompt([{ + const { mediaType } = await inquirer.prompt({ type: 'list', name: 'mediaType', message: 'Movie or a show?', choices: Object.keys(config.mediaPaths) - }]); - const { tmdbId } = await inquirer.prompt([{ + }); + const { tmdbId } = await inquirer.prompt({ name: 'tmdbId', message: 'Enter the TMDB ID:' - }]); + }); + + if (!tmdbId) { + console.warn('⚠️ TMDB ID is required.') + return; + } + const { posterUrl, backdropUrl } = await inquirer.prompt([{ name: 'posterUrl', message: 'Enter the poster url:', - validate: isValidTMDBUrl + validate: (val) => { + if (!val) return true; + return isValidTMDBUrl(val); + } }, { name: 'backdropUrl', message: 'Enter the backdrop url:', - validate: isValidTMDBUrl + validate: (val) => { + if (!val) return true; + return isValidTMDBUrl(val); + } }]); - const types = [{ type: 'poster', url: posterUrl }, - { type: 'backdrop', url: backdropUrl }]; + const types = [{ type: 'poster', url: posterUrl }, { type: 'backdrop', url: backdropUrl }]; for (const { type, url } of types) { - const fileName = `${type}-${tmdbId}.jpg`; + if (!url) continue; + + const ext = path.extname(new URL(url).pathname) || '.jpg'; + const fileName = `${type}-${tmdbId}${ext}`; const targetSubPath = config.mediaPaths[mediaType][type]; if (!targetSubPath) { @@ -57,20 +90,23 @@ export const downloadWatchingImages = async () => { const targetDir = path.join(config.storageDir, targetSubPath); const finalPath = path.join(targetDir, fileName); - await fs.ensureDir(targetDir); - await downloadImage(url, finalPath); - - console.log(`✅ Saved ${type} to: ${finalPath}`); + await overwriteImageDownloadPrompt(url, finalPath, fileName); } } export const downloadArtistImage = async () => { const config = await loadConfig(); - const { artistName, imageUrl } = await inquirer.prompt([{ + const { artistName } = await inquirer.prompt({ name: 'artistName', message: 'Enter the artist name:' - }, - { + }); + + if (!artistName) { + console.warn('⚠️ Artist name is required.') + return; + } + + const { imageUrl } = await inquirer.prompt({ name: 'imageUrl', message: 'Enter the artist image url:', validate: (val) => { @@ -81,29 +117,41 @@ export const downloadArtistImage = async () => { return '❌ Must be a valid url.'; } } - }]); + }); + const sanitizedName = sanitizeMediaString(artistName); - const fileName = `${sanitizedName}.jpg`; + const ext = path.extname(new URL(imageUrl).pathname) || '.jpg'; + const fileName = `${sanitizedName}${ext}`; const targetDir = path.join(config.storageDir, 'artists'); const finalPath = path.join(targetDir, fileName); - await fs.ensureDir(targetDir); - await downloadImage(imageUrl, finalPath); - - console.log(`✅ Saved artist image to: ${finalPath}`); + await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName); } export const downloadAlbumImage = async () => { const config = await loadConfig(); - const { artistName, albumName, imageUrl } = await inquirer.prompt([{ + + const { artistName } = await inquirer.prompt({ name: 'artistName', message: 'Enter the artist name:' - }, - { + }); + + if (!artistName) { + console.warn('⚠️ Artist name is required.'); + return; + } + + const { albumName } = await inquirer.prompt({ name: 'albumName', message: 'Enter the album name:' - }, - { + }); + + if (!albumName) { + console.warn('⚠️ Album name is required.'); + return; + } + + const { imageUrl } = await inquirer.prompt({ name: 'imageUrl', message: 'Enter the album image url:', validate: (val) => { @@ -114,31 +162,36 @@ export const downloadAlbumImage = async () => { return '❌ Must be a valid url.'; } } - }]); + }); + const artistSlug = sanitizeMediaString(artistName); const albumSlug = sanitizeMediaString(albumName); - const fileName = `${artistSlug}-${albumSlug}.jpg`; + const ext = path.extname(new URL(imageUrl).pathname) || '.jpg'; + const fileName = `${artistSlug}-${albumSlug}${ext}`; const targetDir = path.join(config.storageDir, config.albumPath); const finalPath = path.join(targetDir, fileName); - await fs.ensureDir(targetDir); - await downloadImage(imageUrl, finalPath); - - console.log(`✅ Saved album image to: ${finalPath}`); -} + await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName); +}; export const downloadBookImage = async () => { const config = await loadConfig(); - const { isbn, bookTitle, imageUrl } = await inquirer.prompt([{ + const { isbn } = await inquirer.prompt({ name: 'isbn', message: 'Enter the ISBN (no spaces):', validate: (val) => /^[a-zA-Z0-9-]+$/.test(val) || 'ISBN must contain only letters, numbers, or hyphens' - }, - { + }); + const { bookTitle } = await inquirer.prompt({ name: 'bookTitle', message: 'Enter the book title:' - }, - { + }); + + if (!bookTitle) { + console.warn('⚠️ Book title is required.'); + return; + } + + const { imageUrl } = await inquirer.prompt({ name: 'imageUrl', message: 'Enter the book cover image URL:', validate: (val) => { @@ -149,26 +202,24 @@ export const downloadBookImage = async () => { return 'Must be a valid URL'; } } - }]); + }); const titleSlug = sanitizeMediaString(bookTitle); - const fileName = `${isbn}-${titleSlug}.jpg`; + const ext = path.extname(new URL(imageUrl).pathname) || '.jpg'; + const fileName = `${isbn}-${titleSlug}${ext}`; const targetDir = path.join(config.storageDir, config.bookPath); const finalPath = path.join(targetDir, fileName); - await fs.ensureDir(targetDir); - await downloadImage(imageUrl, finalPath); - - console.log(`✅ Saved book cover to: ${finalPath}`); + await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName); } export const downloadAsset = async () => { - const { type } = await inquirer.prompt([{ + const { type } = await inquirer.prompt({ type: 'list', name: 'type', message: 'What type of asset are you downloading?', choices: ['movie/show', 'artist', 'album', 'book'] - }]); + }); if (type === 'artist') { await downloadArtistImage(); diff --git a/cli/package-lock.json b/cli/package-lock.json index 886c734..688103d 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "coryd", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "coryd", - "version": "1.0.0", + "version": "1.1.0", "dependencies": { "chalk": "^5.4.1", "commander": "^14.0.0", diff --git a/cli/package.json b/cli/package.json index 2e3b6a7..dd8b5d4 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,7 +1,7 @@ { "name": "coryd", - "version": "1.0.0", - "description": "The CLI for my site that helps manage assets and other changes.", + "version": "1.1.0", + "description": "The CLI for my site to run scripts, manage and download assets.", "type": "module", "bin": { "coryd": "./bin/index.js"