UNPKG

@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.

246 lines (210 loc) 6.19 kB
const path = require('path'); const fs = require('fs'); const Utils = require('./utils'); let chromium; try { ({ chromium } = require('playwright')); } catch (err) { throw new Error( 'The --browser path requires the optional `playwright` dependency. ' + 'Install it with `npm install playwright` and re-run.' ); } class BrowserManager { constructor(options = {}) { this.browser = null; this.context = null; this.page = null; this.config = { headless: true, debug: false, timeout: 30000, stealth: true, ...options }; } async initBrowser() { if (this.browser) return true; try { this.browser = await chromium.launch({ headless: this.config.headless, args: [ '--disable-blink-features=AutomationControlled', '--disable-web-security', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-extensions' ] }); this.context = await this.browser.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', viewport: { width: 1280, height: 800 }, deviceScaleFactor: 1, hasTouch: false, ignoreHTTPSErrors: true, locale: 'id-ID', timezoneId: 'Asia/Jakarta' }); if (this.config.stealth) { await this._applyStealth(); } this.page = await this.context.newPage(); await this._setHeaders(); return true; } catch (error) { console.error('Failed to initialize browser:', error); return false; } } async _applyStealth() { await this.context.addInitScript(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); window.chrome = { runtime: {} }; Object.defineProperty(navigator, 'languages', { get: () => ['id-ID', 'id', 'en-US', 'en'] }); }); } async _setHeaders() { await this.page.setExtraHTTPHeaders({ 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7', 'sec-ch-ua': '"Not(A:Brand";v="99", "Chromium";v="124"', 'sec-ch-ua-mobile': '?0' }); } async loadCookies(cookiePath) { if (!this.context) return false; try { if (fs.existsSync(cookiePath)) { const rawCookieContent = fs.readFileSync(cookiePath, 'utf8'); const cookieData = JSON.parse(rawCookieContent); const cookies = this._parseCookieData(cookieData); if (cookies.length > 0) { await this.context.addCookies(cookies); return true; } } return false; } catch (error) { console.error('Error loading cookies:', error); return false; } } /** * Sets a custom cookie string for authentication * This is used with the cookie rotation feature * * @param {string} cookieString - The cookie string to set * @returns {Promise<boolean>} - Whether the operation was successful */ async setCustomCookie(cookieString) { if (!this.context || !cookieString) return false; try { const cookies = this._parseCookieString(cookieString); if (cookies.length > 0) { await this.context.addCookies(cookies); return true; } return false; } catch (error) { console.error('Error setting custom cookie:', error); return false; } } _parseCookieData(cookieData) { const cookies = []; if (typeof cookieData === 'string') { return this._parseCookieString(cookieData); } if (cookieData && typeof cookieData === 'object') { for (const [name, value] of Object.entries(cookieData)) { cookies.push({ name, value, domain: Utils.HOST, path: '/' }); } } return cookies; } _parseCookieString(cookieString) { const cookies = []; const pairs = cookieString.split(';'); for (const pair of pairs) { if (pair.includes('=')) { const [name, value] = pair.split('=', 2); cookies.push({ name: name.trim(), value: value.trim(), domain: Utils.HOST, path: '/' }); } } return cookies; } async saveCookies(cookiePath) { if (!this.context) return false; try { const cookies = await this.context.cookies(); const cookieObj = {}; cookies.forEach(c => { cookieObj[c.name] = c.value; }); const cookieDir = path.dirname(cookiePath); if (!fs.existsSync(cookieDir)) { fs.mkdirSync(cookieDir, { recursive: true }); } fs.writeFileSync(cookiePath, JSON.stringify(cookieObj, null, 2), 'utf8'); return true; } catch (error) { console.error('Error saving cookies:', error); return false; } } async navigateTo(url, options = {}) { if (!this.page) return null; try { await this.page.goto(url, { waitUntil: 'domcontentloaded', timeout: this.config.timeout, ...options }); if (this.config.stealth) { await this.page.waitForTimeout(200); } return await this.page.content(); } catch (error) { console.error('Navigation error:', error); return null; } } async checkCloudflare() { if (!this.page) return false; try { return await this.page.evaluate(() => { const title = document.title; const body = document.body ? document.body.innerText : ''; return ( title.includes('Cloudflare') || title.includes('Attention Required') || body.includes('Checking your browser') || body.includes('DDoS protection') || document.querySelector('#cf-error-details') !== null ); }); } catch (error) { return false; } } async close() { if (this.browser) { await this.browser.close(); this.browser = null; this.context = null; this.page = null; } } } module.exports = BrowserManager;