UNPKG

bookgrabs

Version:

Interactive CLI tool for LibGen ebook searches and downloads with batch processing support

131 lines (112 loc) 4.37 kB
import fs from 'fs'; import { parseArgs, showUsage } from './src/config.js'; import { searchLibGen } from './src/search.js'; import { downloadBook, promptUser } from './src/download.js'; import { standardizeAuthorTitle } from './src/ai.js'; import { parseCSV, processBatchBooks, saveReportAndSummary } from './src/csv.js'; import { scoreResult, displayResults } from './src/utils.js'; // Global abort controller for handling cancellation let globalAbortController = null; // Signal handler for graceful exit function handleSignal(signal) { console.log(`\n\nReceived ${signal}. Cancelling operations and exiting...`); if (globalAbortController) { globalAbortController.abort(); } // Give some time for cleanup setTimeout(() => { console.log('Exiting gracefully.'); process.exit(0); }, 1000); } // Set up signal handlers process.on('SIGINT', () => handleSignal('SIGINT')); process.on('SIGTERM', () => handleSignal('SIGTERM')); process.on('SIGQUIT', () => handleSignal('SIGQUIT')); // Parse command line arguments const { author: targetAuthor, title: targetTitle, query, csvFile, searchOptions } = parseArgs(); // Show usage if no query or CSV file provided if (!query && !csvFile) { showUsage(); process.exit(1); } // Main execution function async function main() { // Create global abort controller globalAbortController = new AbortController(); if (csvFile) { // Batch processing mode try { const csvContent = await fs.promises.readFile(csvFile, 'utf8'); const books = parseCSV(csvContent); // Create log file for command line batch processing const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const logFileName = `batch_download_log_${timestamp}.txt`; const { report, downloadedBooks, skippedCount } = await processBatchBooks(books, searchOptions, logFileName); await saveReportAndSummary(report, downloadedBooks, logFileName); } catch (error) { if (error.name === 'AbortError' || error.message.includes('cancelled')) { console.log('\nBatch processing cancelled by user.'); process.exit(0); } else { console.error(`Error reading CSV file "${csvFile}":`, error.message); process.exit(1); } } } else { // Interactive mode try { const results = await searchLibGen(query, searchOptions, globalAbortController.signal); if (results.length === 0) { console.log('No results found.'); return; } // Score and sort results for interactive mode const scoredResults = results.map(result => { result.score = scoreResult(result, targetAuthor, targetTitle); return result; }); scoredResults.sort((a, b) => { if (b.score !== a.score) { return b.score - a.score; } return parseInt(b.year) - parseInt(a.year); }); const topResults = scoredResults.slice(0, 10); displayResults(topResults, targetAuthor, targetTitle); const selectedBook = await promptUser(topResults); if (selectedBook) { // Standardize author and title using GPT console.log('\nStandardizing author and title...'); const standardized = await standardizeAuthorTitle(selectedBook.author, selectedBook.title); console.log(`Standardized: ${standardized.author} - ${standardized.title}`); // Update the selected book with standardized values const standardizedBook = { ...selectedBook, author: standardized.author, title: standardized.title }; // Pass all scored results for potential retries await downloadBook(standardizedBook, scoredResults, globalAbortController.signal); } } catch (error) { if (error.name === 'AbortError' || error.message.includes('cancelled')) { console.log('\nSearch cancelled by user.'); process.exit(0); } else { console.error('Unexpected error:', error); process.exit(1); } } } } // Run the main function main().catch(error => { if (error.name === 'AbortError' || error.message.includes('cancelled')) { console.log('\nOperation cancelled by user.'); process.exit(0); } else { console.error('Unexpected error:', error); process.exit(1); } });