@doedja/kbbi-js
Version:
JavaScript library and CLI for the official KBBI (Kamus Besar Bahasa Indonesia) dictionary. Anonymous lookups out of the box, optional cookie auth for etymology.
111 lines (92 loc) • 3.06 kB
JavaScript
/**
* KBBI-JS
*
* Anonymous lookups use the built-in HTTPS stack (`fetch`). The optional
* Playwright fallback is only loaded when the caller explicitly requests it,
* so the package stays useful without a 130 MB Chromium download.
*/
const KBBIParser = require('./lib/parser');
const Utils = require('./lib/utils');
const Auth = require('./lib/auth');
const { HttpClient } = require('./lib/http');
const { KBBIScraper } = require('./scrape');
class KBBI {
constructor(options = {}) {
this.options = {
headless: true,
debug: false,
timeout: 20000,
useBrowser: false,
...options
};
this.auth = options.auth || new Auth();
}
async lookup(word) {
if (!word) throw new Error('No word provided');
if (this.options.useBrowser) {
return this._lookupWithBrowser(word);
}
const http = new HttpClient({ timeout: this.options.timeout });
const cookieValue = await this.auth.getRandomCookie();
if (cookieValue) {
http.jar.setRaw('.AspNet.ApplicationCookie', cookieValue);
}
const { ok, status, text } = await http.getText(Utils.buildUrl(word));
if (!ok) {
throw new Error(`KBBI returned HTTP ${status} for "${word}"`);
}
const parser = new KBBIParser(text);
const authenticated = parser.checkAuthentication();
const { entries, mirip } = parser.parseEntries();
return {
word,
authenticated,
entries,
mirip: mirip || []
};
}
async scrape(word) {
if (!word) throw new Error('No word provided');
const scraper = new KBBIScraper({
headless: this.options.headless,
debug: this.options.debug,
timeout: this.options.timeout,
useBrowser: this.options.useBrowser,
silent: this.options.silent,
auth: this.auth
});
return scraper.scrapeWord(word);
}
async _lookupWithBrowser(word) {
const BrowserManager = require('./lib/browser');
const { CloudflareBlockError } = require('./lib/errors');
const browser = new BrowserManager({
headless: this.options.headless,
debug: this.options.debug
});
try {
await browser.initBrowser();
const cookieString = await this.auth.getCookieString();
if (cookieString) {
await browser.setCustomCookie(cookieString);
}
const html = await browser.navigateTo(Utils.buildUrl(word));
if (!html) throw new Error('Failed to fetch page content');
const isCloudflare = await browser.checkCloudflare();
if (isCloudflare) {
if (!this.options.headless) {
await browser.page.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 45000 });
} else {
throw new CloudflareBlockError();
}
}
const parser = new KBBIParser(html);
const authenticated = parser.checkAuthentication();
const { entries, mirip } = parser.parseEntries();
return { word, authenticated, entries, mirip: mirip || [] };
} finally {
await browser.close();
}
}
}
module.exports = KBBI;