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
JavaScript
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;