diff --git a/src/utils/puppeteerHandler.ts b/src/utils/puppeteerHandler.ts new file mode 100644 index 0000000..b2c14c6 --- /dev/null +++ b/src/utils/puppeteerHandler.ts @@ -0,0 +1,121 @@ +import puppeteer, { Browser, Page, ElementHandle } from 'puppeteer'; + +/** + * An abstract class to handle common Puppeteer operations. + */ +export abstract class PuppeteerHandler { + private browser: Browser | null = null; + private page: Page | null = null; + + /** + * Opens a new Puppeteer browser instance. + */ + protected async openBrowser() { + this.browser = await puppeteer.launch({ + headless: process.env.NODE_ENV === 'production' ? 'new' : false, + slowMo: process.env.NODE_ENV === 'production' ? 0 : 50, + timeout: 10000, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + '--disable-accelerated-2d-canvas', + ], + }); + } + + /** + * Opens a new page in the current browser instance and navigates to the specified URL. + * @param {string} url - The URL to navigate to. + */ + protected async openPage(url: string) { + if (!this.browser) { + throw new Error('Browser is not open'); + } + + this.page = await this.browser.newPage(); + await this.page.goto(url, { waitUntil: 'networkidle2', timeout: 10000 }); + } + + /** + * Closes the Puppeteer browser instance. + */ + protected async closeBrowser() { + if (this.browser) { + await this.browser.close(); + this.browser = null; + } + } + + /** + * Closes the current page in the browser. + */ + protected async closePage() { + if (this.page) { + await this.page.close(); + this.page = null; + } + } + + /** + * Waits for an element matching the given selector to appear in the current page. + * @param {string} selector - The selector to wait for. + */ + protected async waitForSelector(selector: string) { + if (!this.page) { + throw new Error('Page is not open'); + } + + await this.page.waitForSelector(selector); + } + + /** + * Clicks on an element with the specified selector. + * @param {string} selector - The selector of the element to click. + */ + protected async click(selector: string) { + if (!this.page) { + throw new Error('Page is not open'); + } + + await this.page.click(selector); + } + + /** + * Gets an element with the specified selector. + * @param {string} selector - The selector of the element to get. + * @returns {Promise | null>} - A Promise that resolves with the ElementHandle or null if not found. + */ + protected async getElement(selector: string): Promise | null> { + if (!this.page) { + throw new Error('Page is not open'); + } + + return this.page.$(selector); + } + + /** + * Gets all elements matching the specified selector. + * @param {string} selector - The selector of the elements to get. + * @returns {Promise[]>} - A Promise that resolves with an array of ElementHandles. + */ + protected async getElements(selector: string): Promise[]> { + if (!this.page) { + throw new Error('Page is not open'); + } + + return this.page.$$(selector); + } + + /** + * Types the specified text into an element with the given selector. + * @param {string} selector - The selector of the input element. + * @param {string} text - The text to type. + */ + protected async type(selector: string, text: string) { + const element = await this.getElement(selector); + if (element) { + await element.type(text); + } + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3d62c06..9ee9f9d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,41 +1,67 @@ import axios from 'axios'; -class Utils { - imageFileToBase64 = async (image: any) => { +class Utils { + /** + * Converts an image file to base64 encoding. + * @param {any} image - The image to convert. + * @returns {Promise} - The base64-encoded image. + */ + async imageFileToBase64(image) { const file = await image.screenshot({ encoding: "base64" }); return file; } - sleep = (ms: number) => { + /** + * Sleeps for a specified number of milliseconds. + * @param {number} ms - The number of milliseconds to sleep. + * @returns {Promise} - A Promise that resolves after sleeping. + */ + sleep(ms) { console.log(`Sleeping for ${ms}ms`); return new Promise(resolve => setTimeout(resolve, ms)); } - clearString = (value: string) => { + /** + * Clears unwanted characters and whitespace from a string. + * @param {string} value - The input string to clear. + * @returns {string} - The cleaned string. + */ + clearString(value) { const stringClear = value.replace(/(<([^>]+)>)/gi, "").replace(/(\r\n|\n|\r)/gm, "").replace(/\s+/g, " ").trim(); return stringClear; } - convertStringToDecimal = (value: string) => { + /** + * Converts a formatted currency string to a decimal number. + * @param {string} value - The currency string to convert. + * @returns {number} - The decimal representation of the currency. + */ + convertStringToDecimal(value) { return Number(value.replace('R$', '').replace('.', '').replace(',', '.').replace('R$ ', '')); } - - removeAccents(str: string) { - return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); - } - - webhook = async (url: string, method?: string, header?: any, body?: any) => { - - this.request(url, method ? method : 'POST', header ? header : { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'Detran-PI Scraper' - }, body ? body : {}); + /** + * Removes diacritics (accents) from a string. + * @param {string} str - The input string with diacritics. + * @returns {string} - The string with diacritics removed. + */ + removeAccents(str) { + return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } - request = async (url: string, method: string, headers: any, body: any) => { - + /** + * Sends an HTTP request using Axios. + * @param {string} url - The URL to send the request to. + * @param {string} [method='POST'] - The HTTP method (default is POST). + * @param {object} [header] - The request headers. + * @param {object} [body] - The request body. + * @returns {Promise} - A Promise that resolves with the response data. + */ + async request(url, method = 'POST', header = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'Detran-PI Scraper' + }, body = {}) { const response = await axios.request({ url, method, @@ -44,9 +70,23 @@ class Utils { }); return response.data; - } + /** + * Sends an HTTP request to a specified URL using the provided method, headers, and body. + * @param {string} url - The URL to send the request to. + * @param {string} [method] - The HTTP method (default is POST). + * @param {object} [header] - The request headers. + * @param {object} [body] - The request body. + * @returns {Promise} + */ + async webhook(url, method, header, body) { + this.request(url, method || 'POST', header || { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'Detran-PI Scraper' + }, body || {}); + } } -export default new Utils() \ No newline at end of file +export default new Utils();