UNPKG

hadith-collections

Version:

A comprehensive npm package for searching and browsing hadith collections with Arabic and English support

508 lines (442 loc) • 17.5 kB
#!/usr/bin/env node const autocomplete = require('inquirer-autocomplete-standalone').default; const HadithDB = require('./index.js'); class HadithCLI { constructor() { this.hadithDb = new HadithDB(); } async init() { console.log('šŸŒ™ Welcome to Hadith Collections CLI'); console.log('═══════════════════════════════════════\n'); try { await this.hadithDb.connect(); await this.showCollections(); } catch (error) { console.error('āŒ Error:', error.message); } finally { await this.hadithDb.close(); } } async showCollections() { const collections = await this.hadithDb.getCollections(); const answer = await autocomplete({ message: 'šŸ“š Select a hadith collection:', source: async (input) => { const filtered = collections.filter(collection => { const searchText = `${collection.title_en} ${collection.title}`.toLowerCase(); return input ? searchText.includes(input.toLowerCase()) : true; }); const collectionChoices = filtered.map(collection => ({ name: `${collection.title_en} | ${collection.title}`, value: collection })); // Add export option at the top collectionChoices.unshift({ name: 'šŸ“„ Export All Collections as JSON', value: 'export_collections_json' }); return collectionChoices; } }); if (answer === 'export_collections_json') { await this.exportCollectionsAsJson(collections); return await this.showCollections(); } else if (answer) { await this.showBooks(answer); } } async exportCollectionsAsJson(collections) { const collectionsData = { collections: collections.map(collection => ({ id: collection.id, type: collection.type, title_english: collection.title_en, title_arabic: collection.title, short_description_english: collection.short_description_en, short_description_arabic: collection.short_description, numbering_source_english: collection.numbering_source_en, numbering_source_arabic: collection.numbering_source, has_volumes: collection.has_volumes, has_books: collection.has_books, has_chapters: collection.has_chapters, status: collection.status, last_updated: collection.last_updated })), metadata: { exported_at: new Date().toISOString(), total_collections: collections.length } }; console.log('\nšŸ“„ Collections Export (JSON):'); console.log('═'.repeat(50)); console.log(JSON.stringify(collectionsData, null, 2)); // Wait for user to press Enter before continuing await new Promise(resolve => { process.stdout.write('\nPress Enter to continue...'); process.stdin.once('data', () => resolve()); }); } async showBooks(collection) { console.log(`\nšŸ“– Books in ${collection.title_en} | ${collection.title}:`); console.log('─'.repeat(50)); const books = await this.hadithDb.getBooks(collection.id); if (books.length === 0) { console.log('No books found in this collection.'); return await this.showCollections(); } const bookChoices = books.map(book => { const englishTitle = book.title_en || `Book ${book.display_number}`; const arabicTitle = book.title || ''; return { name: `Book ${book.display_number}: ${englishTitle} | ${arabicTitle}`, value: book }; }); // Add back navigation option bookChoices.unshift({ name: '← Back to Collections', value: 'back' }); const answer = await autocomplete({ message: 'šŸ“– Select a book:', source: async (input) => { const filtered = bookChoices.filter(choice => { if (choice.value === 'back') return true; const searchText = choice.name.toLowerCase(); return input ? searchText.includes(input.toLowerCase()) : true; }); return filtered; } }); if (answer === 'back') { return await this.showCollections(); } else if (answer) { await this.showChapters(collection, answer); } } async showChapters(collection, book) { console.log(`\nšŸ“„ Chapters in ${book.title_en || book.title}:`); console.log('─'.repeat(50)); const chapters = await this.hadithDb.getChapters(collection.id, book.id); if (chapters.length === 0) { console.log('No chapters found in this book.'); console.log('šŸ“š Showing hadiths directly from this book...\n'); return await this.showHadiths(collection, book, null); } const chapterChoices = chapters.map(chapter => { // Use English title if available, otherwise Arabic title let displayTitle = chapter.title_en && chapter.title_en.trim() !== '' ? chapter.title_en : chapter.title; // If title is still just "ŲØŲ§ŲØ" or empty, show it as is with both languages if (displayTitle === 'ŲØŲ§ŲØ' || !displayTitle) { const arabicTitle = chapter.title || 'ŲØŲ§ŲØ'; const englishTitle = chapter.title_en && chapter.title_en.trim() !== '' ? chapter.title_en : 'Chapter'; displayTitle = `${englishTitle} | ${arabicTitle}`; } else { // Show both English and Arabic when both are available if (chapter.title_en && chapter.title_en.trim() !== '' && chapter.title && chapter.title !== 'ŲØŲ§ŲØ') { displayTitle = `${chapter.title_en} | ${chapter.title}`; } } return { name: `Chapter ${chapter.number}: ${displayTitle}`, value: chapter }; }); // Add navigation options chapterChoices.unshift( { name: '← Back to Books', value: 'back' }, { name: 'šŸ“š View All Hadiths in This Book', value: 'all_hadiths' }, { name: 'šŸ“„ Export Book Info as JSON', value: 'export_book_json' }, { name: 'šŸ“‹ Show Only Chapters with Meaningful Titles', value: 'filter_chapters' } ); const answer = await autocomplete({ message: 'šŸ“„ Select a chapter:', source: async (input) => { const filtered = chapterChoices.filter(choice => { if (choice.value === 'back' || choice.value === 'all_hadiths') return true; const searchText = choice.name.toLowerCase(); return input ? searchText.includes(input.toLowerCase()) : true; }); return filtered; } }); if (answer === 'back') { return await this.showBooks(collection); } else if (answer === 'all_hadiths') { return await this.showHadiths(collection, book, null); } else if (answer === 'filter_chapters') { return await this.showFilteredChapters(collection, book, chapters); } else if (answer === 'export_book_json') { await this.exportBookAsJson(collection, book, chapters); return await this.showChapters(collection, book); } else if (answer) { await this.showHadiths(collection, book, answer); } } async showFilteredChapters(collection, book, allChapters) { console.log(`\nšŸ“„ Chapters with Meaningful Titles in ${book.title_en || book.title}:`); console.log('─'.repeat(50)); const filteredChapters = allChapters.filter(chapter => { // Filter to only show chapters that have meaningful titles return (chapter.title && chapter.title !== 'ŲØŲ§ŲØ') || (chapter.title_en && chapter.title_en.trim() !== ''); }); const chapterChoices = filteredChapters.map(chapter => { // Use English title if available, otherwise Arabic title let displayTitle = chapter.title_en && chapter.title_en.trim() !== '' ? chapter.title_en : chapter.title; // Show both English and Arabic when both are available if (chapter.title_en && chapter.title_en.trim() !== '' && chapter.title && chapter.title !== 'ŲØŲ§ŲØ') { displayTitle = `${chapter.title_en} | ${chapter.title}`; } return { name: `Chapter ${chapter.number}: ${displayTitle}`, value: chapter }; }); // Add navigation options chapterChoices.unshift( { name: '← Back to All Chapters', value: 'back' }, { name: 'šŸ“š View All Hadiths in This Book', value: 'all_hadiths' } ); const answer = await autocomplete({ message: `šŸ“„ Select a chapter (${filteredChapters.length} of ${allChapters.length} chapters with meaningful titles):`, source: async (input) => { const filtered = chapterChoices.filter(choice => { if (choice.value === 'back' || choice.value === 'all_hadiths') return true; const searchText = choice.name.toLowerCase(); return input ? searchText.includes(input.toLowerCase()) : true; }); return filtered; } }); if (answer === 'back') { return await this.showChapters(collection, book); } else if (answer === 'all_hadiths') { return await this.showHadiths(collection, book, null); } else if (answer) { await this.showHadiths(collection, book, answer); } } async showHadiths(collection, book, chapter) { const chapterText = chapter ? ` - ${chapter.title_en || chapter.title}` : ''; console.log(`\nšŸ“œ Hadiths in ${book.title_en || book.title}${chapterText}:`); console.log('─'.repeat(50)); // Get hadiths from the collection const options = { limit: 50, bookId: book.id }; if (chapter) { options.chapterId = chapter.id; } const hadiths = await this.hadithDb.getHadithsByCollection(collection.id, options); if (hadiths.length === 0) { console.log('No hadiths found.'); return chapter ? await this.showChapters(collection, book) : await this.showBooks(collection); } const hadithChoices = hadiths.map((hadith, index) => ({ name: `Hadith ${hadith.display_number || (index + 1)}: ${hadith.content.substring(0, 100)}...`, value: hadith })); // Add navigation options hadithChoices.unshift( { name: '← Back to ' + (chapter ? 'Chapters' : 'Books'), value: 'back' }, { name: 'šŸŽ² Show Random Hadith', value: 'random' } ); const answer = await autocomplete({ message: 'šŸ“œ Select a hadith to read:', source: async (input) => { const filtered = hadithChoices.filter(choice => { if (choice.value === 'back' || choice.value === 'random') return true; const searchText = choice.name.toLowerCase(); return input ? searchText.includes(input.toLowerCase()) : true; }); return filtered; } }); if (answer === 'back') { return chapter ? await this.showChapters(collection, book) : await this.showBooks(collection); } else if (answer === 'random') { const randomHadith = await this.hadithDb.getRandomHadith(collection.id); if (randomHadith) { await this.displayHadith(randomHadith); } return await this.showHadiths(collection, book, chapter); } else if (answer) { await this.displayHadith(answer); return await this.showHadiths(collection, book, chapter); } } async displayHadith(hadith) { // Prepare the hadith data object const hadithData = { urn: hadith.urn, collection_id: hadith.collection_id, book_id: hadith.book_id, chapter_id: hadith.chapter_id, display_number: hadith.display_number, order_in_book: hadith.order_in_book, arabic: { narrator_prefix: hadith.narrator_prefix || '', content: hadith.content || '', narrator_postfix: hadith.narrator_postfix || '', content_diacless: hadith.content_diacless || '', narrator_prefix_diacless: hadith.narrator_prefix_diacless || '', narrator_postfix_diacless: hadith.narrator_postfix_diacless || '' }, english: null, metadata: { comments: hadith.comments || '', grades: hadith.grades || '', narrators: hadith.narrators || '', related_hadiths: hadith.related_hadiths || '' } }; // Try to get English translation try { const englishHadith = await this.hadithDb.getEnglishHadithByUrn(hadith.urn); if (englishHadith && englishHadith.content) { hadithData.english = { narrator_prefix: englishHadith.narrator_prefix || '', content: englishHadith.content || '', narrator_postfix: englishHadith.narrator_postfix || '', reference: englishHadith.reference || '', comments: englishHadith.comments || '', grades: englishHadith.grades || '' }; } } catch (error) { // English translation not available or error occurred } // Output as formatted JSON console.log('\n' + JSON.stringify(hadithData, null, 2)); // Wait for user to press Enter before continuing await new Promise(resolve => { process.stdout.write('\nPress Enter to continue...'); process.stdin.once('data', () => resolve()); }); } async exportBookAsJson(collection, book, chapters) { const bookData = { collection: { id: collection.id, title_english: collection.title_en, title_arabic: collection.title, short_description_english: collection.short_description_en, short_description_arabic: collection.short_description, type: collection.type, status: collection.status }, book: { id: book.id, collection_id: book.collection_id, display_number: book.display_number, order_in_collection: book.order_in_collection, title_english: book.title_en, title_arabic: book.title, intro_english: book.intro_en, intro_arabic: book.intro, hadith_start: book.hadith_start, hadith_end: book.hadith_end, hadith_count: book.hadith_count }, chapters: chapters.map(chapter => ({ id: chapter.id, collection_id: chapter.collection_id, book_id: chapter.book_id, number: chapter.number, title_english: chapter.title_en, title_arabic: chapter.title, intro_english: chapter.intro_en, intro_arabic: chapter.intro, ending_english: chapter.ending_en, ending_arabic: chapter.ending })), metadata: { exported_at: new Date().toISOString(), total_chapters: chapters.length } }; console.log('\nšŸ“„ Book Export (JSON):'); console.log('═'.repeat(50)); console.log(JSON.stringify(bookData, null, 2)); // Wait for user to press Enter before continuing await new Promise(resolve => { process.stdout.write('\nPress Enter to continue...'); process.stdin.once('data', () => resolve()); }); } async searchHadiths() { const query = await autocomplete({ message: 'šŸ” Enter search term:', source: async (input) => { if (!input || input.length < 2) { return [{ name: 'Type at least 2 characters to search...', value: null }]; } return [{ name: `Search for: "${input}"`, value: input }]; } }); if (query) { console.log(`\nšŸ” Searching for: "${query}"`); console.log('─'.repeat(50)); const results = await this.hadithDb.search(query, { limit: 20 }); // Output search results as JSON const searchResults = { query: query, total_results: results.total, arabic_results_count: results.arabic.length, english_results_count: results.english.length, results: { arabic: results.arabic.map(hadith => ({ urn: hadith.urn, collection_id: hadith.collection_id, book_id: hadith.book_id, chapter_id: hadith.chapter_id, display_number: hadith.display_number, narrator_prefix: hadith.narrator_prefix, content: hadith.content, narrator_postfix: hadith.narrator_postfix, grades: hadith.grades, narrators: hadith.narrators })), english: results.english.map(hadith => ({ arabic_urn: hadith.arabic_urn, urn: hadith.urn, collection_id: hadith.collection_id, narrator_prefix: hadith.narrator_prefix, content: hadith.content, narrator_postfix: hadith.narrator_postfix, reference: hadith.reference, grades: hadith.grades })) } }; console.log(JSON.stringify(searchResults, null, 2)); // Wait for user to press Enter before continuing await new Promise(resolve => { process.stdout.write('\nPress Enter to continue...'); process.stdin.once('data', () => resolve()); }); } } } // Main execution async function main() { const cli = new HadithCLI(); // Handle Ctrl+C gracefully process.on('SIGINT', async () => { console.log('\n\nšŸ‘‹ Goodbye! May Allah bless you.'); await cli.hadithDb.close(); process.exit(0); }); await cli.init(); } // Only run if this file is executed directly if (require.main === module) { main().catch(console.error); } module.exports = HadithCLI;