chore(*): use prettier for formatting

This commit is contained in:
Cory Dransfeldt 2025-06-14 16:43:12 -07:00
parent 6c659fe1d0
commit 029caaaa9e
No known key found for this signature in database
73 changed files with 1390 additions and 794 deletions

View file

@ -12,18 +12,27 @@ import { runTasksMenu } from '../lib/tasks/index.js';
process.on('unhandledRejection', (err) => handleExitError(err, 'Unhandled rejection'));
process.on('uncaughtException', (err) => handleExitError(err, 'Uncaught exception'));
program.name('coryd').description('🪄 Handle tasks, run commands or jobs, download things and have fun.').version('3.2.5');
program.command('init').description('Initialize CLI and populate required config.').action(async () => {
const { initConfig } = await import('../lib/config.js');
await initConfig();
});
program
.name('coryd')
.description('🪄 Handle tasks, run commands or jobs, download things and have fun.')
.version('3.2.5');
program
.command('init')
.description('Initialize CLI and populate required config.')
.action(async () => {
const { initConfig } = await import('../lib/config.js');
await initConfig();
});
program.command('run [script]').description('Run site scripts and commands.').action(runRootScript);
program.command('tasks').description('Handle repeated tasks.').action(runTasksMenu);
program.command('jobs').description('Trigger jobs and scripts.').action(runJobsMenu);
program.command('download').description('Download, name and store image assets.').action(async () => {
const { downloadAsset } = await import('../lib/download.js');
await downloadAsset();
});
program
.command('download')
.description('Download, name and store image assets.')
.action(async () => {
const { downloadAsset } = await import('../lib/download.js');
await downloadAsset();
});
if (process.argv.length <= 2) {
const ascii = figlet.textSync('coryd.dev', { horizontalLayout: 'default' });

View file

@ -14,42 +14,50 @@ const ASSET_TYPES = ['poster', 'backdrop'];
dotenv.config({ path: path.resolve(__dirname, '..', '..', '.env') });
export const initConfig = async () => {
const existingConfig = await fs.pathExists(CONFIG_PATH) ? await fs.readJson(CONFIG_PATH) : {};
const existingConfig = (await fs.pathExists(CONFIG_PATH)) ? await fs.readJson(CONFIG_PATH) : {};
const config = { ...existingConfig };
if (config.storageDir) {
const { updateStorage } = await inquirer.prompt([{
type: 'confirm',
name: 'updateStorage',
message: `Storage directory is already set to "${config.storageDir}". Do you want to update it?`,
default: false
}]);
const { updateStorage } = await inquirer.prompt([
{
type: 'confirm',
name: 'updateStorage',
message: `Storage directory is already set to "${config.storageDir}". Do you want to update it?`,
default: false
}
]);
if (updateStorage) {
const { storageDir } = await inquirer.prompt([{
name: 'storageDir',
message: 'Where is your storage root directory?',
validate: fs.pathExists
}]);
const { storageDir } = await inquirer.prompt([
{
name: 'storageDir',
message: 'Where is your storage root directory?',
validate: fs.pathExists
}
]);
config.storageDir = storageDir;
}
} else {
const { storageDir } = await inquirer.prompt([{
name: 'storageDir',
message: 'Where is your storage root directory?',
validate: fs.pathExists
}]);
const { storageDir } = await inquirer.prompt([
{
name: 'storageDir',
message: 'Where is your storage root directory?',
validate: fs.pathExists
}
]);
config.storageDir = storageDir;
}
const { customize } = await inquirer.prompt([{
type: 'confirm',
name: 'customize',
message: 'Do you want to customize default media paths?',
default: false
}]);
const { customize } = await inquirer.prompt([
{
type: 'confirm',
name: 'customize',
message: 'Do you want to customize default media paths?',
default: false
}
]);
config.mediaPaths = {};
@ -63,11 +71,13 @@ export const initConfig = async () => {
let subpath = defaultPath;
if (customize) {
const response = await inquirer.prompt([{
name: 'subpath',
message: `Subpath for ${mediaType}/${assetType} (relative to storage root):`,
default: defaultPath
}]);
const response = await inquirer.prompt([
{
name: 'subpath',
message: `Subpath for ${mediaType}/${assetType} (relative to storage root):`,
default: defaultPath
}
]);
subpath = response.subpath;
}
@ -113,28 +123,34 @@ export const initConfig = async () => {
: 'Media assets/books';
if (config.directus?.apiUrl) {
const { updateApiUrl } = await inquirer.prompt([{
type: 'confirm',
name: 'updateApiUrl',
message: `Directus API URL is already set to "${config.directus.apiUrl}". Do you want to update it?`,
default: false
}]);
const { updateApiUrl } = await inquirer.prompt([
{
type: 'confirm',
name: 'updateApiUrl',
message: `Directus API URL is already set to "${config.directus.apiUrl}". Do you want to update it?`,
default: false
}
]);
if (updateApiUrl) {
const { apiUrl } = await inquirer.prompt([{
name: 'apiUrl',
message: 'Enter your Directus instance URL:',
validate: input => input.startsWith('http') || 'Must be a valid URL'
}]);
const { apiUrl } = await inquirer.prompt([
{
name: 'apiUrl',
message: 'Enter your Directus instance URL:',
validate: (input) => input.startsWith('http') || 'Must be a valid URL'
}
]);
config.directus.apiUrl = apiUrl;
}
} else {
const { apiUrl } = await inquirer.prompt([{
name: 'apiUrl',
message: 'Enter your Directus URL:',
validate: input => input.startsWith('http') || 'Must be a valid URL'
}]);
const { apiUrl } = await inquirer.prompt([
{
name: 'apiUrl',
message: 'Enter your Directus URL:',
validate: (input) => input.startsWith('http') || 'Must be a valid URL'
}
]);
config.directus = { ...(config.directus || {}), apiUrl };
}
@ -150,7 +166,7 @@ export const initConfig = async () => {
};
export const loadConfig = async () => {
if (!await fs.pathExists(CONFIG_PATH)) {
if (!(await fs.pathExists(CONFIG_PATH))) {
console.error('❌ Config not found. Run `coryd init` first.');
process.exit(1);
}

View file

@ -32,9 +32,9 @@ const request = async (method, endpoint, body = null) => {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_TOKEN}`,
Authorization: `Bearer ${API_TOKEN}`
},
body: body ? JSON.stringify(body) : null,
body: body ? JSON.stringify(body) : null
});
if (!res.ok) {
@ -63,8 +63,8 @@ export const searchItems = async (collection, query = '', filters = {}) => {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_TOKEN}`,
},
Authorization: `Bearer ${API_TOKEN}`
}
});
const data = await res.json();
@ -76,6 +76,7 @@ export const searchItems = async (collection, query = '', filters = {}) => {
}
};
export const updateItem = async (collection, id, values) => await request('PATCH', `${collection}/${id}`, values);
export const updateItem = async (collection, id, values) =>
await request('PATCH', `${collection}/${id}`, values);
export const createItem = async (collection, values) => await request('POST', collection, values);

View file

@ -25,13 +25,13 @@ export const promptForMultipleRelations = async (collection, label = collection)
type: 'checkbox',
name: 'selected',
message: `✔ Select ${label} to add:`,
choices: results.map(item => ({
choices: results.map((item) => ({
name: item.name || item.title || item.id,
value: item.id
}))
});
selected.forEach(id => selectedIds.add(id));
selected.forEach((id) => selectedIds.add(id));
const { again } = await inquirer.prompt({
type: 'confirm',

View file

@ -7,17 +7,22 @@ import { loadConfig } from './config.js';
import { sanitizeMediaString } from './sanitize.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const downloadImage = (url, dest) => new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
const downloadImage = (url, dest) =>
new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
https.get(url, response => {
if (response.statusCode !== 200) return reject(new Error(`Failed to download. Status: ${response.statusCode}`));
https
.get(url, (response) => {
if (response.statusCode !== 200)
return reject(new Error(`Failed to download. Status: ${response.statusCode}`));
response.pipe(file);
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';
response.pipe(file);
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 overwriteImageDownloadPrompt = async (url, finalPath, fileName) => {
await fs.ensureDir(path.dirname(finalPath));
@ -60,25 +65,30 @@ export const downloadWatchingImages = async () => {
return;
}
const { posterUrl, backdropUrl } = await inquirer.prompt([{
name: 'posterUrl',
message: 'Enter the poster url:',
validate: (val) => {
if (!val) return true;
const { posterUrl, backdropUrl } = await inquirer.prompt([
{
name: 'posterUrl',
message: 'Enter the poster url:',
validate: (val) => {
if (!val) return true;
return isValidTMDBUrl(val);
}
},
{
name: 'backdropUrl',
message: 'Enter the backdrop url:',
validate: (val) => {
if (!val) return true;
return isValidTMDBUrl(val);
}
},
{
name: 'backdropUrl',
message: 'Enter the backdrop url:',
validate: (val) => {
if (!val) return true;
return isValidTMDBUrl(val);
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) {
if (!url) continue;
@ -98,7 +108,7 @@ export const downloadWatchingImages = async () => {
await overwriteImageDownloadPrompt(url, finalPath, fileName);
}
}
};
export const downloadArtistImage = async () => {
const config = await loadConfig();
@ -134,7 +144,7 @@ export const downloadArtistImage = async () => {
const finalPath = path.join(targetDir, fileName);
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
}
};
export const downloadAlbumImage = async () => {
const config = await loadConfig();
@ -190,7 +200,8 @@ export const downloadBookImage = async () => {
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'
validate: (val) =>
/^[a-zA-Z0-9-]+$/.test(val) || 'ISBN must contain only letters, numbers, or hyphens'
});
const { bookTitle } = await inquirer.prompt({
name: 'bookTitle',
@ -223,8 +234,7 @@ export const downloadBookImage = async () => {
const finalPath = path.join(targetDir, fileName);
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
}
};
export const downloadAsset = async () => {
const { type } = await inquirer.prompt({
@ -243,4 +253,4 @@ export const downloadAsset = async () => {
} else {
await downloadWatchingImages();
}
}
};

View file

@ -1,5 +1,8 @@
export const handleExitError = (err, type = 'Unhandled error') => {
const isExit = err?.name === 'ExitPromptError' || err?.code === 'ERR_CANCELED' || err?.message?.includes('SIGINT');
const isExit =
err?.name === 'ExitPromptError' ||
err?.code === 'ERR_CANCELED' ||
err?.message?.includes('SIGINT');
if (isExit) {
console.log('\n👋 Exiting. Cya!\n');
@ -8,4 +11,4 @@ export const handleExitError = (err, type = 'Unhandled error') => {
console.error(`${type}:`, err);
process.exit(1);
}
};

View file

@ -45,12 +45,14 @@ export const runJobsMenu = async () => {
apiUrl: `${config.url}/api/artist-import.php`,
tokenEnvVar: 'ARTIST_IMPORT_TOKEN',
method: 'POST',
paramsPrompt: [{
type: 'input',
name: 'artistId',
message: 'Enter the Navidrome artist ID:',
validate: input => input ? true : 'Artist ID is required'
}]
paramsPrompt: [
{
type: 'input',
name: 'artistId',
message: 'Enter the Navidrome artist ID:',
validate: (input) => (input ? true : 'Artist ID is required')
}
]
},
{
name: '📖 Import book',
@ -58,12 +60,14 @@ export const runJobsMenu = async () => {
apiUrl: `${config.url}/api/book-import.php`,
tokenEnvVar: 'BOOK_IMPORT_TOKEN',
method: 'POST',
paramsPrompt: [{
type: 'input',
name: 'isbn',
message: 'Enter the book\'s ISBN:',
validate: input => input ? true : 'ISBN is required'
}]
paramsPrompt: [
{
type: 'input',
name: 'isbn',
message: "Enter the book's ISBN:",
validate: (input) => (input ? true : 'ISBN is required')
}
]
},
{
name: '📽 Import movie or show',
@ -72,19 +76,20 @@ export const runJobsMenu = async () => {
tokenEnvVar: 'WATCHING_IMPORT_TOKEN',
method: 'POST',
tokenIncludeInParams: true,
paramsPrompt: [{
type: 'input',
name: 'tmdb_id',
message: 'Enter the TMDB ID:',
validate: (input) =>
/^\d+$/.test(input) ? true : 'Please enter a valid TMDB ID'
},
{
type: 'list',
name: 'media_type',
message: 'Is this a movie or a show?',
choices: ['movie', 'show']
}]
paramsPrompt: [
{
type: 'input',
name: 'tmdb_id',
message: 'Enter the TMDB ID:',
validate: (input) => (/^\d+$/.test(input) ? true : 'Please enter a valid TMDB ID')
},
{
type: 'list',
name: 'media_type',
message: 'Is this a movie or a show?',
choices: ['movie', 'show']
}
]
},
{
name: '📺 Import upcoming TV seasons',
@ -92,17 +97,20 @@ export const runJobsMenu = async () => {
apiUrl: `${config.url}/api/seasons-import.php`,
tokenEnvVar: 'SEASONS_IMPORT_TOKEN',
method: 'POST'
}];
}
];
const { selectedJob } = await inquirer.prompt([{
type: 'list',
name: 'selectedJob',
message: 'Select a job to run:',
choices: JOBS.map((job, index) => ({
name: job.name,
value: index
}))
}]);
const { selectedJob } = await inquirer.prompt([
{
type: 'list',
name: 'selectedJob',
message: 'Select a job to run:',
choices: JOBS.map((job, index) => ({
name: job.name,
value: index
}))
}
]);
const job = JOBS[selectedJob];
@ -124,7 +132,7 @@ export const runJobsMenu = async () => {
const runCurl = async ({
urlEnvVar,
apiUrl = "",
apiUrl = '',
tokenEnvVar,
method = 'POST',
name,

View file

@ -15,12 +15,14 @@ export const runRootScript = async (scriptArg) => {
let script = scriptArg;
if (!script) {
const { selected } = await inquirer.prompt([{
type: 'list',
name: 'selected',
message: 'Select a script to run:',
choices: Object.keys(scripts)
}]);
const { selected } = await inquirer.prompt([
{
type: 'list',
name: 'selected',
message: 'Select a script to run:',
choices: Object.keys(scripts)
}
]);
script = selected;
}
@ -39,4 +41,4 @@ export const runRootScript = async (scriptArg) => {
console.error(`❌ Failed to run script "${script}"`);
process.exit(1);
}
}
};

View file

@ -6,6 +6,6 @@ export const sanitizeMediaString = (input) => {
const slugified = cleaned.replace(/[\s-]+/g, '-').toLowerCase();
return slugified.replace(/^-+|-+$/g, '');
}
};
export const removeUrlProtocol = (url) => url ? url.replace(/^https?:\/\//, '') : '';
export const removeUrlProtocol = (url) => (url ? url.replace(/^https?:\/\//, '') : '');

View file

@ -8,7 +8,7 @@ export const addBlockedRobot = async () => {
initDirectusClient(config);
const robots = await searchItems('robots', '/');
let rootRobot = robots.find(r => r.path === '/');
let rootRobot = robots.find((r) => r.path === '/');
if (!rootRobot) {
console.log(' No robots entry for `/` found. Creating one...');
@ -23,7 +23,7 @@ export const addBlockedRobot = async () => {
const { userAgent } = await inquirer.prompt({
name: 'userAgent',
message: '🤖 Enter the user-agent string to block:',
validate: input => !!input || 'User-agent cannot be empty'
validate: (input) => !!input || 'User-agent cannot be empty'
});
const createdAgent = await createItem('user_agents', {

View file

@ -1,6 +1,12 @@
import inquirer from 'inquirer';
import { loadConfig } from '../config.js';
import { initDirectusClient, getDirectusClient, searchItems, createItem, updateItem } from '../directus/client.js';
import {
initDirectusClient,
getDirectusClient,
searchItems,
createItem,
updateItem
} from '../directus/client.js';
export const addEpisodeToShow = async () => {
const config = await loadConfig();
@ -10,7 +16,7 @@ export const addEpisodeToShow = async () => {
const directus = getDirectusClient();
const showResults = await inquirer.prompt({
name: 'query',
message: 'Search for a show:',
message: 'Search for a show:'
});
const matches = await searchItems('shows', showResults.query);
@ -24,34 +30,35 @@ export const addEpisodeToShow = async () => {
type: 'list',
name: 'showId',
message: 'Select a show:',
choices: matches.map(s => ({
choices: matches.map((s) => ({
name: s.title || s.name || s.id,
value: s.id,
})),
value: s.id
}))
});
const { season_number, episode_number, plays } = await inquirer.prompt([
{
name: 'season_number',
message: 'Season number:',
validate: val => !isNaN(val),
validate: (val) => !isNaN(val)
},
{
name: 'episode_number',
message: 'Episode number:',
validate: val => !isNaN(val),
validate: (val) => !isNaN(val)
},
{
name: 'plays',
message: 'Play count:',
default: 0,
validate: val => !isNaN(val),
},
validate: (val) => !isNaN(val)
}
]);
const existing = await searchItems('episodes', `${season_number} ${episode_number}`);
const match = existing.find(e =>
Number(e.season_number) === Number(season_number) &&
Number(e.episode_number) === Number(episode_number) &&
e.show === showId
const match = existing.find(
(e) =>
Number(e.season_number) === Number(season_number) &&
Number(e.episode_number) === Number(episode_number) &&
e.show === showId
);
if (match) {
@ -59,7 +66,7 @@ export const addEpisodeToShow = async () => {
type: 'confirm',
name: 'update',
message: `Episode exists. Update play count from ${match.plays ?? 0} to ${plays}?`,
default: true,
default: true
});
if (update) {
@ -74,7 +81,7 @@ export const addEpisodeToShow = async () => {
season_number: Number(season_number),
episode_number: Number(episode_number),
plays: Number(plays),
show: showId,
show: showId
});
console.log(`📺 Created episode S${season_number}E${episode_number}`);

View file

@ -12,12 +12,12 @@ export const addLinkToShare = async () => {
{
name: 'title',
message: '📝 Title for the link:',
validate: input => !!input || 'Title is required'
validate: (input) => !!input || 'Title is required'
},
{
name: 'link',
message: '🔗 URL to share:',
validate: input => input.startsWith('http') || 'Must be a valid URL'
validate: (input) => input.startsWith('http') || 'Must be a valid URL'
},
{
name: 'description',
@ -26,7 +26,7 @@ export const addLinkToShare = async () => {
},
{
name: 'authorQuery',
message: '👤 Search for an author:',
message: '👤 Search for an author:'
}
]);
@ -38,13 +38,17 @@ export const addLinkToShare = async () => {
type: 'confirm',
name: 'shouldCreate',
message: '❌ No authors found. Do you want to create a new one?',
default: true,
default: true
});
if (!shouldCreate) return;
const { name, url, mastodon, rss, json, newsletter, blogroll } = await inquirer.prompt([
{ name: 'name', message: '👤 Author name:', validate: input => !!input || 'Name is required' },
{
name: 'name',
message: '👤 Author name:',
validate: (input) => !!input || 'Name is required'
},
{ name: 'url', message: '🔗 URL (optional):', default: '' },
{ name: 'mastodon', message: '🐘 Mastodon handle (optional):', default: '' },
{ name: 'rss', message: '📡 RSS feed (optional):', default: '' },
@ -69,13 +73,13 @@ export const addLinkToShare = async () => {
type: 'list',
name: 'author',
message: 'Select an author:',
choices: authorMatches.map(a => {
choices: authorMatches.map((a) => {
const cleanUrl = removeUrlProtocol(a.url);
const display = cleanUrl ? `${a.name} (${cleanUrl})` : a.name;
return {
name: display,
value: a.id,
value: a.id
};
})
});
@ -88,7 +92,7 @@ export const addLinkToShare = async () => {
while (true) {
const { query } = await inquirer.prompt({
name: 'query',
message: '🏷 Search for tags (or leave blank to finish):',
message: '🏷 Search for tags (or leave blank to finish):'
});
const trimmedQuery = query.trim();
@ -106,7 +110,7 @@ export const addLinkToShare = async () => {
type: 'checkbox',
name: 'selected',
message: '✔ Select tags to add:',
choices: tags.map(tag => ({ name: tag.name, value: tag.id }))
choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
});
tagIds.push(...selected);
@ -115,7 +119,7 @@ export const addLinkToShare = async () => {
type: 'confirm',
name: 'again',
message: 'Search and select more tags?',
default: false,
default: false
});
if (!again) break;
@ -126,7 +130,7 @@ export const addLinkToShare = async () => {
link,
description,
author,
link_tags: tagIds.map(tagId => ({ tags_id: tagId })),
link_tags: tagIds.map((tagId) => ({ tags_id: tagId })),
date: new Date().toISOString()
});

View file

@ -18,34 +18,36 @@ export const addPost = async () => {
initDirectusClient(config);
const { title, description, content, featured } = await inquirer.prompt([{
name: 'title',
message: '📝 Title:',
validate: input => !!input || 'Title is required'
},
{
name: 'description',
message: '🗒 Description:',
default: ''
},
{
name: 'content',
message: '📄 Content:',
default: ''
},
{
type: 'confirm',
name: 'featured',
message: '⭐ Featured?',
default: false
}]);
const { title, description, content, featured } = await inquirer.prompt([
{
name: 'title',
message: '📝 Title:',
validate: (input) => !!input || 'Title is required'
},
{
name: 'description',
message: '🗒 Description:',
default: ''
},
{
name: 'content',
message: '📄 Content:',
default: ''
},
{
type: 'confirm',
name: 'featured',
message: '⭐ Featured?',
default: false
}
]);
let tagIds = [];
while (true) {
const { query } = await inquirer.prompt({
name: 'query',
message: '🏷 Search for tags (or leave blank to finish):',
message: '🏷 Search for tags (or leave blank to finish):'
});
const trimmedQuery = query.trim();
@ -63,7 +65,7 @@ export const addPost = async () => {
type: 'checkbox',
name: 'selected',
message: '✔ Select tags to add:',
choices: tags.map(tag => ({ name: tag.name, value: tag.id }))
choices: tags.map((tag) => ({ name: tag.name, value: tag.id }))
});
tagIds.push(...selected);
@ -72,7 +74,7 @@ export const addPost = async () => {
type: 'confirm',
name: 'again',
message: 'Search and select more tags?',
default: false,
default: false
});
if (!again) break;
@ -113,7 +115,7 @@ export const addPost = async () => {
type: 'list',
name: 'itemId',
message: `Select an item from ${collection}:`,
choices: results.map(item => ({
choices: results.map((item) => ({
name: item.title || item.name || item.id,
value: item.id
}))
@ -161,13 +163,16 @@ export const addPost = async () => {
type: 'checkbox',
name: 'selected',
message: `✔ Select ${mediaType} to associate:`,
choices: matches.map(m => ({
choices: matches.map((m) => ({
name: m.name_string || m.title || m.name || m.label || m.id,
value: m.id
}))
});
if (selected.length) associatedMediaPayload[`${mediaType}`] = selected.map(id => ({ [`${mediaType}_id`]: id }));
if (selected.length)
associatedMediaPayload[`${mediaType}`] = selected.map((id) => ({
[`${mediaType}_id`]: id
}));
}
}
@ -178,7 +183,7 @@ export const addPost = async () => {
content,
featured,
date: new Date().toISOString(),
post_tags: tagIds.map(tagId => ({ tags_id: tagId })),
post_tags: tagIds.map((tagId) => ({ tags_id: tagId })),
blocks: selectedBlocks,
...associatedMediaPayload
};

View file

@ -10,7 +10,7 @@ const TASKS = [
{ name: '🔗 Add link to share', handler: addLinkToShare },
{ name: ' Add episode to show', handler: addEpisodeToShow },
{ name: '📚 Update reading progress', handler: updateReadingProgress },
{ name: '🤖 Block robot', handler: addBlockedRobot },
{ name: '🤖 Block robot', handler: addBlockedRobot }
];
export const runTasksMenu = async () => {
@ -19,7 +19,7 @@ export const runTasksMenu = async () => {
type: 'list',
name: 'task',
message: 'Select a task to perform:',
choices: TASKS.map(t => ({ name: t.name, value: t.handler }))
choices: TASKS.map((t) => ({ name: t.name, value: t.handler }))
}
]);

View file

@ -19,7 +19,7 @@ export const updateReadingProgress = async () => {
type: 'list',
name: 'bookId',
message: '📚 Select a book to update progress:',
choices: readingBooks.map(book => {
choices: readingBooks.map((book) => {
const title = book.title || book.name || `Book #${book.id}`;
const progress = book.progress ?? 0;
@ -32,10 +32,10 @@ export const updateReadingProgress = async () => {
const { progress } = await inquirer.prompt({
name: 'progress',
message: '📕 New progress percentage (0100):',
validate: input => {
validate: (input) => {
const num = Number(input);
return !isNaN(num) && num >= 0 && num <= 100 || 'Enter a number from 0 to 100';
return (!isNaN(num) && num >= 0 && num <= 100) || 'Enter a number from 0 to 100';
}
});