UNPKG

ephrem

Version:

Ephrem is a light-weight API wrapper for API.Bible, built using NodeJS and Typescript. Ephrem validates bible references and fetches scripture text corresponding to the references.

325 lines 10.7 kB
import fs from "node:fs"; import { ABB_TO_ID_MAPPING_PATH, BIBLES_DATA_PATH, BOOKS_DATA_PATH, getPassageFromApi, NAMES_TO_BIBLES_PATH } from "./api-bible.js"; import { BOOK_IDs } from "./book-ids.js"; import { BaseEphremError, normalizeBookName } from "./utils.js"; class BookIdNotFoundError extends BaseEphremError { context; constructor(bookId, bibleId) { super("Book ID could not be found in the Bible"); this.name = "BookIdNotFoundError"; this.context = { bibleId, bookId }; } } class FallbackBibleNotFoundError extends BaseEphremError { context; constructor(input, fallbackBibleAbbreviation) { super( "Input does not indicate a bible abbreviation, and no fallback Bible Abbreviation was provided" ); this.name = "FallbackBibleNotFoundError"; this.context = { fallbackBibleAbbreviation, input }; } } class UnknownBibleAbbreviationError extends BaseEphremError { context; constructor(bibleAbbreviation, mappingFile) { super("Bible Abbreviation could not be found in the mapping file"); this.name = "UnknownBibleAbbreviationError"; this.context = { bibleAbbreviation, mappingFile }; } } class InvalidReferenceError extends BaseEphremError { context; constructor(input) { super( "Input reference is invalid. Please check the reference and try again." ); this.name = "InvalidReferenceError"; this.context = { input }; } } class BibleNotAvailableError extends BaseEphremError { context; constructor(bibleId) { super( "Requested Bible is not available. Please check if the API.Bible key has access to this Bible." ); this.name = "BibleNotAvailableError"; this.context = { bibleId }; } } class BookNotInBibleError extends BaseEphremError { context; constructor(input, bookId, availableBookIds, bibleName, bibleNameLocal) { super( "Requested Book is not available in this Bible. Please check the reference and try again." ); this.name = "BookNotInBibleError"; this.context = { availableBookIds, bibleName, bibleNameLocal, bookId, input }; } } const getBibleIdFromAbbreviation = async (bibleAbbreviation) => { const bibleMap = JSON.parse( await fs.promises.readFile(ABB_TO_ID_MAPPING_PATH, "utf-8") ); return bibleMap[bibleAbbreviation]; }; const getBibleDetails = async (bibleId) => { const biblesData = JSON.parse( await fs.promises.readFile(BIBLES_DATA_PATH, "utf-8") ); return biblesData[bibleId]; }; const getBookDetails = async (bookId, bibleId) => { const books = JSON.parse( await fs.promises.readFile(BOOKS_DATA_PATH, "utf-8") ); return books.find((book) => book.id === bookId && book.bibleId === bibleId); }; const bookIsInBible = (bookId, bibleId, books) => { return books.some((book) => book.id === bookId && book.bibleId === bibleId); }; const getBookIdInBible = (normalizedBookName, bibleId, bookNamesToBibles) => { if (!(normalizedBookName in bookNamesToBibles)) { return void 0; } return bookNamesToBibles[normalizedBookName][bibleId]; }; const getKeyOfMaxValue = (obj) => { return Object.keys(obj).reduce( (a, b) => obj[a] > obj[b] ? a : b, Object.keys(obj)[0] ); }; const getBookIdByMajority = (normalizedBookName, bibleMap, bookNamesToBibles) => { if (!(normalizedBookName in bookNamesToBibles)) { return void 0; } const biblesWithBook = bookNamesToBibles[normalizedBookName]; const biblesToConsider = Object.values(bibleMap); const voteTally = {}; const alreadyConsideredBibleIds = []; for (const [bibleId, bookId] of Object.entries(biblesWithBook)) { if (alreadyConsideredBibleIds.includes(bibleId) || !biblesToConsider.includes(bibleId)) { continue; } voteTally[bookId] ||= 0; voteTally[bookId] += 1; alreadyConsideredBibleIds.push(bibleId); } return getKeyOfMaxValue(voteTally); }; const getBookId = async (bookName, bibleId) => { const bookNamesToBibles = JSON.parse( await fs.promises.readFile(NAMES_TO_BIBLES_PATH, "utf-8") ); const normalizedBookName = normalizeBookName(bookName); let bookId = void 0; if (bibleId !== void 0) { bookId = getBookIdInBible(normalizedBookName, bibleId, bookNamesToBibles); } if (bookId === void 0) { const bibleMap = JSON.parse( await fs.promises.readFile(ABB_TO_ID_MAPPING_PATH, "utf-8") ); bookId = getBookIdByMajority( normalizedBookName, bibleMap, bookNamesToBibles ); } if (bookId && bookId in BOOK_IDs) { return bookId; } return void 0; }; const extractTranslationAndBookChapterVerse = (input) => { const bibleAbbreviation = /\(([^)]+)\)/.exec(input)?.[1]; const bookChapterVerse = bibleAbbreviation ? input.replace(`(${bibleAbbreviation})`, "") : input; if (bookChapterVerse.includes("-") && bookChapterVerse.includes(":")) { const hyphenIndex = bookChapterVerse.indexOf("-"); const colonIndex = bookChapterVerse.indexOf(":"); if (hyphenIndex < colonIndex) { throw new InvalidReferenceError(input); } } return { bibleAbbreviation, bookChapterVerse }; }; const splitChapterAndVerse = (chapterVerse) => { const chapterVerseParts = chapterVerse.split("-").map((trimPart) => trimPart.trim()); let chapterEnd, chapterStart, verseEnd, verseStart; if (chapterVerseParts[0]?.includes(":")) { [chapterStart, verseStart] = chapterVerseParts[0].split(":"); if (chapterVerseParts[1]) { chapterEnd = chapterVerseParts[1].includes(":") ? chapterVerseParts[1].split(":")[0] : void 0; verseEnd = chapterVerseParts[1].includes(":") ? chapterVerseParts[1].split(":")[1] : chapterVerseParts[1]; } } else { [chapterStart, chapterEnd] = chapterVerseParts; } return { chapterEnd, chapterStart, verseEnd, verseStart }; }; const parseReference = async (input, fallbackBibleAbbreviation) => { const { bibleAbbreviation, bookChapterVerse } = extractTranslationAndBookChapterVerse(input); let [bookName, chapterVerse] = bookChapterVerse.split(/\s+(?=\d)/); if (!bookName || !chapterVerse) { throw new InvalidReferenceError(input); } let targetBibleAbbreviation; if (bibleAbbreviation !== void 0) { targetBibleAbbreviation = bibleAbbreviation; } else { if (!fallbackBibleAbbreviation) { throw new FallbackBibleNotFoundError(input, fallbackBibleAbbreviation); } targetBibleAbbreviation = fallbackBibleAbbreviation; } const bibleId = await getBibleIdFromAbbreviation(targetBibleAbbreviation); if (bibleId === void 0) { throw new UnknownBibleAbbreviationError( targetBibleAbbreviation, ABB_TO_ID_MAPPING_PATH ); } const bookId = await getBookId(bookName.trim(), bibleId); if (!bookId) { return void 0; } const booksData = JSON.parse( await fs.promises.readFile(BOOKS_DATA_PATH, "utf-8") ); const biblesData = JSON.parse( await fs.promises.readFile(BIBLES_DATA_PATH, "utf-8") ); if (!bookIsInBible(bookId, bibleId, booksData)) { throw new BookNotInBibleError( input, bookId, booksData.filter((book) => book.bibleId === bibleId).map((book) => book.id), biblesData[bibleId].name, biblesData[bibleId].nameLocal ); } const { chapterEnd, chapterStart, verseEnd, verseStart } = splitChapterAndVerse(chapterVerse); return { bibleId, bookId, chapterEnd, chapterStart, verseEnd, verseStart }; }; const createPassageBoundary = (bookId, chapter, verse) => { return verse ? `${bookId}.${chapter}.${verse}` : `${bookId}.${chapter}`; }; const getPassageId = (reference) => { const sections = []; const requiredSection = createPassageBoundary( reference.bookId, reference.chapterStart, reference.verseStart ); let optionalSection = void 0; if (reference.chapterEnd !== void 0 && reference.verseEnd !== void 0) { optionalSection = createPassageBoundary( reference.bookId, reference.chapterEnd, reference.verseEnd ); } else if (reference.chapterEnd !== void 0 && reference.chapterEnd !== reference.chapterStart) { optionalSection = createPassageBoundary( reference.bookId, reference.chapterEnd ); } else if (reference.verseEnd !== void 0 && reference.verseStart !== reference.verseEnd) { optionalSection = createPassageBoundary( reference.bookId, reference.chapterStart, reference.verseEnd ); } sections.push(requiredSection); if (optionalSection) { sections.push(optionalSection); } return sections.join("-").replace(/\s+/g, ""); }; const getPassageFromReference = async (reference, passageOptions) => { const passageId = getPassageId(reference); return await getPassageFromApi(passageId, reference.bibleId, passageOptions); }; const getPassage = async (input, passageOptions, fallbackBibleAbbreviation) => { const reference = await parseReference(input, fallbackBibleAbbreviation); if (!reference) { throw new InvalidReferenceError(input); } return await getPassageFromReference(reference, passageOptions); }; const _getPassageWithDetails = async (passageAndFums, reference) => { const bible = await getBibleDetails(reference.bibleId); if (bible === void 0) { throw new BibleNotAvailableError(reference.bibleId); } const book = await getBookDetails(reference.bookId, reference.bibleId); if (book === void 0) { throw new BookIdNotFoundError(reference.bookId, reference.bibleId); } return { bible, book, fums: passageAndFums.meta, passage: passageAndFums.data }; }; const getPassageWithDetails = async (input, passageOptions, fallbackBibleAbbreviation) => { const passageAndFums = await getPassage( input, passageOptions, fallbackBibleAbbreviation ); const reference = await parseReference(input, fallbackBibleAbbreviation); if (!reference) { throw new InvalidReferenceError(input); } return await _getPassageWithDetails(passageAndFums, reference); }; const getPassageWithDetailsFromReference = async (reference, passageOptions) => { const passageAndFums = await getPassageFromReference( reference, passageOptions ); return await _getPassageWithDetails(passageAndFums, reference); }; export { BibleNotAvailableError, BookIdNotFoundError, BookNotInBibleError, FallbackBibleNotFoundError, InvalidReferenceError, UnknownBibleAbbreviationError, _getPassageWithDetails, getBibleDetails, getBibleIdFromAbbreviation, getBookDetails, getBookId, getPassage, getPassageFromReference, getPassageId, getPassageWithDetails, getPassageWithDetailsFromReference, parseReference }; //# sourceMappingURL=reference.js.map