@surgbc/egw-writings-downloader
Version:
Data extraction and database population tool for EGW Writings
240 lines (239 loc) ⢠9.71 kB
JavaScript
import 'dotenv/config';
import { Command } from 'commander';
import { createAuthManager, createApiClient, EGWDatabase } from '@surgbc/egw-writings-shared';
const program = new Command();
program
.name('egw-downloader')
.description('Download and index EGW Writings content')
.version('1.0.0');
program
.command('languages')
.description('Download and index all available languages')
.action(async () => {
console.log('š Downloading languages...');
const authManager = createAuthManager();
const apiClient = createApiClient(authManager);
const db = new EGWDatabase();
try {
const languages = await apiClient.getLanguages();
console.log(`Found ${languages.length} languages`);
for (const lang of languages) {
db.insertLanguage(lang.code, lang.name, lang.direction);
console.log(`ā
${lang.name} (${lang.code})`);
}
console.log(`š Successfully indexed ${languages.length} languages`);
}
catch (error) {
console.error('ā Error downloading languages:', error);
process.exit(1);
}
finally {
db.close();
}
});
program
.command('books')
.description('Download book metadata for a language')
.option('-l, --lang <language>', 'Language code', 'en')
.option('--limit <number>', 'Limit number of books', '100')
.action(async (options) => {
console.log(`š Downloading books for language: ${options.lang}`);
const authManager = createAuthManager();
const apiClient = createApiClient(authManager);
const db = new EGWDatabase();
try {
// Get folders for the language
const folders = await apiClient.getFolders(options.lang);
console.log(`Found ${folders.length} folders`);
let totalBooks = 0;
// Process all folders and their children
for (const folder of folders) {
if (folder.children) {
for (const subfolder of folder.children) {
if (subfolder.nbooks > 0) {
console.log(`š Processing folder: ${subfolder.name} (${subfolder.nbooks} books)`);
const books = await apiClient.getBooksByFolder(subfolder.folder_id);
const limit = parseInt(options.limit);
const booksToProcess = books.slice(0, limit);
for (const book of booksToProcess) {
db.insertBook(book);
totalBooks++;
console.log(` ā
${book.title} by ${book.author}`);
}
}
}
}
}
console.log(`š Successfully indexed ${totalBooks} books`);
}
catch (error) {
console.error('ā Error downloading books:', error);
process.exit(1);
}
finally {
db.close();
}
});
program
.command('content')
.description('Download full content for books')
.option('-l, --lang <language>', 'Language code', 'en')
.option('-b, --book <bookId>', 'Specific book ID to download')
.option('--limit <number>', 'Limit number of books', '10')
.action(async (options) => {
console.log('š Downloading book content...');
const authManager = createAuthManager();
const apiClient = createApiClient(authManager);
const db = new EGWDatabase();
try {
let books;
if (options.book) {
// Download specific book
const book = db.getBook(parseInt(options.book));
if (!book) {
throw new Error(`Book ${options.book} not found in database`);
}
books = [book];
}
else {
// Download books for language
books = db.getBooks(options.lang).slice(0, parseInt(options.limit));
}
console.log(`Processing ${books.length} books...`);
for (const book of books) {
const typedBook = book;
console.log(`\nš Downloading content for: ${typedBook.title}`);
try {
// Get table of contents
const toc = await apiClient.getBookToc(typedBook.book_id);
console.log(` Found ${toc.length} chapters`);
let totalParagraphs = 0;
// Download each chapter
for (const chapter of toc) {
try {
const paragraphs = await apiClient.getChapter(typedBook.book_id, chapter.id);
console.log(` š Chapter "${chapter.title}": ${paragraphs.length} paragraphs`);
// Insert paragraphs
for (const paragraph of paragraphs) {
db.insertParagraph(paragraph, typedBook.book_id, chapter.title);
totalParagraphs++;
}
// Rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
}
catch (chapterError) {
console.error(` ā Error downloading chapter ${chapter.id}:`, chapterError);
}
}
console.log(` ā
Downloaded ${totalParagraphs} paragraphs`);
}
catch (bookError) {
console.error(`ā Error downloading book ${typedBook.book_id}:`, bookError);
}
}
console.log('š Content download complete!');
}
catch (error) {
console.error('ā Error downloading content:', error);
process.exit(1);
}
finally {
db.close();
}
});
program
.command('stats')
.description('Show database statistics')
.action(async () => {
const db = new EGWDatabase();
try {
const stats = db.getStats();
console.log('š Database Statistics:');
console.log(` Languages: ${stats.languages}`);
console.log(` Books: ${stats.books}`);
console.log(` Downloaded Books: ${stats.downloadedBooks}`);
console.log(` Paragraphs: ${stats.paragraphs}`);
const progress = db.getDownloadProgress();
if (progress.length > 0) {
console.log('\nš Recent Download Progress:');
progress.slice(0, 5).forEach((p) => {
console.log(` ${p.task_type}: ${p.completed_items}/${p.total_items || '?'} (${p.status})`);
});
}
}
catch (error) {
console.error('ā Error getting stats:', error);
process.exit(1);
}
finally {
db.close();
}
});
program
.command('quick-start')
.description('Quick setup: download languages, sample books, and content')
.action(async () => {
console.log('š Quick Start: Setting up EGW Writings database...\n');
const authManager = createAuthManager();
const apiClient = createApiClient(authManager);
const db = new EGWDatabase();
try {
// Step 1: Languages
console.log('1ļøā£ Downloading languages...');
const languages = await apiClient.getLanguages();
for (const lang of languages) {
db.insertLanguage(lang.code, lang.name, lang.direction);
}
console.log(`ā
${languages.length} languages indexed\n`);
// Step 2: Books (English only, first 20)
console.log('2ļøā£ Downloading sample books (English)...');
const folders = await apiClient.getFolders('en');
const booksFolder = folders.find(f => f.children?.find(c => c.name === 'Books'))?.children?.find(c => c.name === 'Books');
if (booksFolder) {
const books = await apiClient.getBooksByFolder(booksFolder.folder_id);
const sampleBooks = books.slice(0, 20);
for (const book of sampleBooks) {
db.insertBook(book);
}
console.log(`ā
${sampleBooks.length} books indexed\n`);
// Step 3: Content (first 3 books only)
console.log('3ļøā£ Downloading sample content...');
const contentBooks = sampleBooks.slice(0, 3);
for (const book of contentBooks) {
console.log(` š ${book.title}...`);
try {
const toc = await apiClient.getBookToc(book.book_id);
const firstChapter = toc[0];
if (firstChapter) {
const paragraphs = await apiClient.getChapter(book.book_id, firstChapter.id);
for (const paragraph of paragraphs) {
db.insertParagraph(paragraph, book.book_id, firstChapter.title);
}
console.log(` ā
${paragraphs.length} paragraphs from first chapter`);
}
// Rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
}
catch (error) {
console.log(` ā Error: ${error}`);
}
}
}
console.log('\nš Quick start complete!');
const stats = db.getStats();
console.log('\nš Final Statistics:');
console.log(` Languages: ${stats.languages}`);
console.log(` Books: ${stats.books}`);
console.log(` Paragraphs: ${stats.paragraphs}`);
console.log('\nš” Try: pnpm --filter local-server dev');
}
catch (error) {
console.error('ā Quick start failed:', error);
process.exit(1);
}
finally {
db.close();
}
});
program.parse();