diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index f05d0136..bc154aa7 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -48,7 +48,8 @@ import * as fs from 'fs'; const DateDiff = require('date-diff'); const puppeteer = require('puppeteer'); const Path = require('path'); -import bb, {area, bar, zoom} from "billboard.js"; +import bb, { area, bar, zoom } from "billboard.js"; +import * as request from 'request-promise-native'; /** * Base services of conversation to be called by BASIC which @@ -144,8 +145,8 @@ export class DialogKeywords { const legends_ = legends.split(';'); for (let i = 0; i < legends_.length; i++) { - columns[i] = [legends_[i]]; - columns[i] = columns[i].concat(data); + columns[i] = [legends_[i]]; + columns[i] = columns[i].concat(data); } const definition = { @@ -161,7 +162,7 @@ export class DialogKeywords { width: 200 } }; - + const gbaiName = `${this.min.botId}.gbai`; const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.jpg`); @@ -175,7 +176,7 @@ export class DialogKeywords { ); GBLog.info(`BASIC: Visualization: Chart generated at ${url}.`); - + return url; } @@ -201,6 +202,8 @@ export class DialogKeywords { const f = await frameHandle.contentFrame(); await f.waitForSelector(selector); const element = await f.$(selector); + element['originalSelector'] = selector; + element['frame'] = f; return element; } @@ -218,27 +221,9 @@ export class DialogKeywords { ]); } - /** - * Returns the screenshot of page or element - * - * @example file = SCREENSHOT page - */ - public async getTableData(step, page, selector) { - const data = await page.evaluate(() => { - const rows = document.querySelectorAll(`${selector} tr`); - return Array.from(rows, row => { - const columns = row.querySelectorAll('td'); - return Array.from(columns, column => column.innerText); - }); - }); - //You will now have an array of strings - //[ 'One', 'Two', 'Three', 'Four' ] - console.log(data); - - } /** * Returns the screenshot of page or element @@ -250,27 +235,6 @@ export class DialogKeywords { await e.screenshot({ path: localName }); } - /** - * Performs the download to the .gbdrive Download folder. - * - * @example file = DOWNLOAD page, "tableName", row - */ - public async download(step, page, idOrName, localName) { - - const e = await this.getBySelector(page, idOrName); - const context = await this.browser.newContext({ acceptDownloads: true }); - - var cells = e.rows[0].cells; - - const [download] = await Promise.all([ - page.waitForEvent('download'), - page.click(cells[0]) - ]); - - const path = await download.path(); - - console.log(path); - } /** * Types the text into the text field. @@ -278,9 +242,9 @@ export class DialogKeywords { * @example TYPE page, "elementName", "text" */ public async type(step, page, idOrName, text) { - const e = await this.getBySelector(page, idOrName); - await e.type(text); - } + const e = await this.getBySelector(page, idOrName); + await e.type(text); +} /** * Returns the today data filled in dd/mm/yyyy or mm/dd/yyyy. @@ -288,48 +252,48 @@ export class DialogKeywords { * @example x = TODAY */ public async getOCR(step, localFile) { - const tesseract = require("node-tesseract-ocr") + const tesseract = require("node-tesseract-ocr") - const config = { - lang: "eng", - oem: 1, - psm: 3, - } - - return await tesseract.recognize(localFile, config); + const config = { + lang: "eng", + oem: 1, + psm: 3, } + return await tesseract.recognize(localFile, config); +} + /** * Returns the today data filled in dd/mm/yyyy or mm/dd/yyyy. * * @example x = TODAY */ public async getToday(step) { - let d = new Date(), - month = '' + (d.getMonth() + 1), - day = '' + d.getDate(), - year = d.getFullYear(); + let d = new Date(), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); - if (month.length < 2) { month = '0' + month; } - if (day.length < 2) { day = '0' + day; } + if (month.length < 2) { month = '0' + month; } + if (day.length < 2) { day = '0' + day; } - const contentLocale = this.min.core.getParam( - this.min.instance, - 'Default Content Language', - GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') - ); + const contentLocale = this.min.core.getParam( + this.min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); - switch (contentLocale) { - case 'pt': - return [day, month, year].join('/'); + switch (contentLocale) { + case 'pt': + return [day, month, year].join('/'); - case 'en': - return [month, day, year].join('/'); + case 'en': + return [month, day, year].join('/'); - default: - return [year, month, day].join('/'); - } + default: + return [year, month, day].join('/'); } +} /** * Quits the dialog, currently required to get out of VM context. @@ -337,11 +301,11 @@ export class DialogKeywords { * @example EXIT */ public async exit(step) { - if (this.browser) { - await this.browser.close(); - } - await step.endDialog(); + if (this.browser) { + await this.browser.close(); } + await step.endDialog(); +} /** * Get active tasks. @@ -349,9 +313,9 @@ export class DialogKeywords { * @example list = ACTIVE TASKS */ public async getActiveTasks() { - let s = new HubSpotServices(null, null, process.env.HUBSPOT_KEY); - return await s.getActiveTasks(); - } + let s = new HubSpotServices(null, null, process.env.HUBSPOT_KEY); + return await s.getActiveTasks(); +} /** * Creates a new deal. @@ -359,10 +323,10 @@ export class DialogKeywords { * @example CREATE DEAL dealname, contato, empresa, amount */ public async createDeal(dealName, contact, company, amount) { - let s = new HubSpotServices(null, null, process.env.HUBSPOT_KEY); - let deal = await s.createDeal(dealName, contact, company, amount); - return deal; - } + let s = new HubSpotServices(null, null, process.env.HUBSPOT_KEY); + let deal = await s.createDeal(dealName, contact, company, amount); + return deal; +} /** * Finds contacts in XRM. @@ -370,25 +334,25 @@ export class DialogKeywords { * @example list = FIND CONTACT "Sandra" */ public async fndContact(name) { - let s = new HubSpotServices(null, null, process.env.HUBSPOT_KEY); - return await s.searchContact(name); - } + let s = new HubSpotServices(null, null, process.env.HUBSPOT_KEY); + return await s.searchContact(name); +} public getContentLocaleWithCulture(contentLocale) { - switch (contentLocale) { - case 'pt': - return 'pt-BR'; + switch (contentLocale) { + case 'pt': + return 'pt-BR'; - case 'en': - return 'en-US'; - - default: - return 'en-us'; - } + case 'en': + return 'en-US'; + default: + return 'en-us'; } +} + /** * Returns specified date week day in format 'Mon'. * @@ -397,26 +361,26 @@ export class DialogKeywords { */ public getWeekFromDate(date) { - const contentLocale = this.min.core.getParam( - this.min.instance, - 'Default Content Language', - GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') - ); + const contentLocale = this.min.core.getParam( + this.min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); - let dt = SystemKeywords.getDateFromLocaleString(date, contentLocale); - GBLog.info(`BASIC WEEKDAY contentLocale: ${this.getContentLocaleWithCulture(contentLocale)}`); - GBLog.info(`BASIC WEEKDAY date: ${dt}`); - GBLog.info(dt.toLocaleString(this.getContentLocaleWithCulture(contentLocale), { weekday: 'short' })); + let dt = SystemKeywords.getDateFromLocaleString(date, contentLocale); + GBLog.info(`BASIC WEEKDAY contentLocale: ${this.getContentLocaleWithCulture(contentLocale)}`); + GBLog.info(`BASIC WEEKDAY date: ${dt}`); + GBLog.info(dt.toLocaleString(this.getContentLocaleWithCulture(contentLocale), { weekday: 'short' })); - if (dt) { - if (!(dt instanceof Date)) { - dt = new Date(dt); - } - let week = dt.toLocaleString(this.getContentLocaleWithCulture(contentLocale), { weekday: 'short' }); - return week.substr(0, 3); + if (dt) { + if (!(dt instanceof Date)) { + dt = new Date(dt); } - return 'NULL'; + let week = dt.toLocaleString(this.getContentLocaleWithCulture(contentLocale), { weekday: 'short' }); + return week.substr(0, 3); } + return 'NULL'; +} /** * Returns an object ready to get information about difference in several ways @@ -426,24 +390,24 @@ export class DialogKeywords { * */ public dateDiff(date1, date2, mode) { - let dt1 = date1; - let dt2 = date2; - if (!(dt1 instanceof Date)) { - dt1 = new Date(dt1); - } - if (!(dt2 instanceof Date)) { - dt2 = new Date(dt2); - } - const diff = new DateDiff(date1, date2); - switch (mode) { - case 'year': return diff.years(); - case 'month': return diff.months(); - case 'week': return diff.weeks(); - case 'day': return diff.days(); - case 'hour': return diff.hours(); - case 'minute': return diff.minutes(); - } + let dt1 = date1; + let dt2 = date2; + if (!(dt1 instanceof Date)) { + dt1 = new Date(dt1); } + if (!(dt2 instanceof Date)) { + dt2 = new Date(dt2); + } + const diff = new DateDiff(date1, date2); + switch (mode) { + case 'year': return diff.years(); + case 'month': return diff.months(); + case 'week': return diff.weeks(); + case 'day': return diff.days(); + case 'hour': return diff.hours(); + case 'minute': return diff.minutes(); + } +} /** * Returns specified date week day in format 'Mon'. @@ -453,25 +417,25 @@ export class DialogKeywords { * https://stackoverflow.com/a/1214753/18511 */ public dateAdd(date, mode, units) { - let dateCopy = date; - if (!(dateCopy instanceof Date)) { - dateCopy = new Date(dateCopy); - } - var ret = new Date(dateCopy); //don't change original date - var checkRollover = function () { if (ret.getDate() != date.getDate()) ret.setDate(0); }; - switch (String(mode).toLowerCase()) { - case 'year': ret.setFullYear(ret.getFullYear() + units); checkRollover(); break; - case 'quarter': ret.setMonth(ret.getMonth() + 3 * units); checkRollover(); break; - case 'month': ret.setMonth(ret.getMonth() + units); checkRollover(); break; - case 'week': ret.setDate(ret.getDate() + 7 * units); break; - case 'day': ret.setDate(ret.getDate() + units); break; - case 'hour': ret.setTime(ret.getTime() + units * 3600000); break; - case 'minute': ret.setTime(ret.getTime() + units * 60000); break; - case 'second': ret.setTime(ret.getTime() + units * 1000); break; - default: ret = undefined; break; - } - return ret; + let dateCopy = date; + if (!(dateCopy instanceof Date)) { + dateCopy = new Date(dateCopy); } + var ret = new Date(dateCopy); //don't change original date + var checkRollover = function () { if (ret.getDate() != date.getDate()) ret.setDate(0); }; + switch (String(mode).toLowerCase()) { + case 'year': ret.setFullYear(ret.getFullYear() + units); checkRollover(); break; + case 'quarter': ret.setMonth(ret.getMonth() + 3 * units); checkRollover(); break; + case 'month': ret.setMonth(ret.getMonth() + units); checkRollover(); break; + case 'week': ret.setDate(ret.getDate() + 7 * units); break; + case 'day': ret.setDate(ret.getDate() + units); break; + case 'hour': ret.setTime(ret.getTime() + units * 3600000); break; + case 'minute': ret.setTime(ret.getTime() + units * 60000); break; + case 'second': ret.setTime(ret.getTime() + units * 1000); break; + default: ret = undefined; break; + } + return ret; +} @@ -482,19 +446,19 @@ export class DialogKeywords { * */ public getToLst(array, member) { - if (!array) { - return "" - } - if (array[0] && array[0]['gbarray']) { - array = array.slice(1); - } - array = array.filter((v, i, a) => a.findIndex(t => (t[member] === v[member])) === i); - array = array.filter(function (item, pos) { return item != undefined; }); - array = array.map((item) => { return item[member]; }) - array = array.join(", "); - - return array; + if (!array) { + return "" } + if (array[0] && array[0]['gbarray']) { + array = array.slice(1); + } + array = array.filter((v, i, a) => a.findIndex(t => (t[member] === v[member])) === i); + array = array.filter(function (item, pos) { return item != undefined; }); + array = array.map((item) => { return item[member]; }) + array = array.join(", "); + + return array; +} /** * Returns the specified time in format hh:dd. @@ -503,30 +467,30 @@ export class DialogKeywords { * */ public getHourFromDate(date) { - function addZero(i) { - if (i < 10) { - i = "0" + i; - } - return i; + function addZero(i) { + if (i < 10) { + i = "0" + i; } - - const contentLocale = this.min.core.getParam( - this.min.instance, - 'Default Content Language', - GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') - ); - - let dt = SystemKeywords.getDateFromLocaleString(date, contentLocale); - - if (dt) { - if (!(dt instanceof Date)) { - dt = new Date(dt); - } - return addZero(dt.getHours()) + ':' + addZero(dt.getMinutes()); - } - return 'NULL'; + return i; } + const contentLocale = this.min.core.getParam( + this.min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); + + let dt = SystemKeywords.getDateFromLocaleString(date, contentLocale); + + if (dt) { + if (!(dt instanceof Date)) { + dt = new Date(dt); + } + return addZero(dt.getHours()) + ':' + addZero(dt.getMinutes()); + } + return 'NULL'; +} + /** * Returns current time in format hh:dd. * @@ -534,22 +498,22 @@ export class DialogKeywords { * */ public async getNow() { - const contentLocale = this.min.core.getParam( - this.min.instance, - 'Default Content Language', - GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') - ); + const contentLocale = this.min.core.getParam( + this.min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); - const nowUTC = new Date(); - const now = typeof nowUTC === 'string' ? - new Date(nowUTC) : - nowUTC; + const nowUTC = new Date(); + const now = typeof nowUTC === 'string' ? + new Date(nowUTC) : + nowUTC; - const nowText = now.toLocaleString(this.getContentLocaleWithCulture(contentLocale), - { timeZone: process.env.DEFAULT_TIMEZONE }); + const nowText = now.toLocaleString(this.getContentLocaleWithCulture(contentLocale), + { timeZone: process.env.DEFAULT_TIMEZONE }); - return /\b([0-9]|0[0-9]|1?[0-9]|2[0-3]):[0-5]?[0-9]/.exec(nowText)[0]; - } + return /\b([0-9]|0[0-9]|1?[0-9]|2[0-3]):[0-5]?[0-9]/.exec(nowText)[0]; +} /** * Sends a file to a given mobile. @@ -558,9 +522,9 @@ export class DialogKeywords { * */ public async sendFileTo(step, mobile, filename, caption) { - GBLog.info(`BASIC: SEND FILE TO '${mobile}', filename '${filename}'.`); - return await this.internalSendFile(null, mobile, filename, caption); - } + GBLog.info(`BASIC: SEND FILE TO '${mobile}', filename '${filename}'.`); + return await this.internalSendFile(null, mobile, filename, caption); +} /** * Sends a file to the current user. @@ -569,10 +533,10 @@ export class DialogKeywords { * */ public async sendFile(step, filename, caption) { - const mobile = await this.userMobile(step); - GBLog.info(`BASIC: SEND FILE (current: ${mobile}, filename '${filename}'.`); - return await this.internalSendFile(step, mobile, filename, caption); - } + const mobile = await this.userMobile(step); + GBLog.info(`BASIC: SEND FILE (current: ${mobile}, filename '${filename}'.`); + return await this.internalSendFile(step, mobile, filename, caption); +} /** * Defines the current language of the bot conversation. @@ -581,14 +545,14 @@ export class DialogKeywords { * */ public async setLanguage(step, language) { - const user = await this.min.userProfile.get(step.context, {}); + const user = await this.min.userProfile.get(step.context, {}); - const sec = new SecService(); - user.systemUser = await sec.updateUserLocale(user.systemUser.userId, language); + const sec = new SecService(); + user.systemUser = await sec.updateUserLocale(user.systemUser.userId, language); - await this.min.userProfile.set(step.context, user); - this.user = user; - } + await this.min.userProfile.set(step.context, user); + this.user = user; +} /** * Defines the maximum lines to scan in spreedsheets. @@ -597,11 +561,11 @@ export class DialogKeywords { * */ public async setMaxLines(step, count) { - const user = await this.min.userProfile.get(step.context, {}); - user.basicOptions.maxLines = count; - await this.min.userProfile.set(step.context, user); - this.user = user; - } + const user = await this.min.userProfile.get(step.context, {}); + user.basicOptions.maxLines = count; + await this.min.userProfile.set(step.context, user); + this.user = user; +} /** * Defines the FIND behaviour to consider whole words while searching. @@ -610,24 +574,24 @@ export class DialogKeywords { * */ public async setWholeWord(step, on) { - const user = await this.min.userProfile.get(step.context, {}); - user.basicOptions.wholeWord = (on.trim() === "on"); - await this.min.userProfile.set(step.context, user); - this.user = user; - } + const user = await this.min.userProfile.get(step.context, {}); + user.basicOptions.wholeWord = (on.trim() === "on"); + await this.min.userProfile.set(step.context, user); + this.user = user; +} - /** - * Defines the theme for assets generation. - * - * @example SET THEME "themename" - * - */ - public async setTheme(step, theme) { - const user = await this.min.userProfile.get(step.context, {}); - user.basicOptions.theme = theme.trim(); - await this.min.userProfile.set(step.context, user); - this.user = user; - } + /** + * Defines the theme for assets generation. + * + * @example SET THEME "themename" + * + */ + public async setTheme(step, theme) { + const user = await this.min.userProfile.get(step.context, {}); + user.basicOptions.theme = theme.trim(); + await this.min.userProfile.set(step.context, user); + this.user = user; +} /** * Defines translator behaviour. @@ -636,26 +600,26 @@ export class DialogKeywords { * */ public async setTranslatorOn(step, on) { - const user = await this.min.userProfile.get(step.context, {}); - user.basicOptions.translatorOn = (on.trim() === "on"); - await this.min.userProfile.set(step.context, user); - this.user = user; - } + const user = await this.min.userProfile.get(step.context, {}); + user.basicOptions.translatorOn = (on.trim() === "on"); + await this.min.userProfile.set(step.context, user); + this.user = user; +} /** * Returns the name of the user acquired by WhatsApp API. */ public async userName(step) { - return step ? WhatsappDirectLine.usernames[await this.userMobile(step)] : 'N/A'; - } + return step ? WhatsappDirectLine.usernames[await this.userMobile(step)] : 'N/A'; +} /** * OBSOLETE. */ public async getFrom(step) { - return step ? await this.userMobile(step) : 'N/A'; - } + return step ? await this.userMobile(step) : 'N/A'; +} /** @@ -665,8 +629,8 @@ export class DialogKeywords { * */ public async userMobile(step) { - return GBMinService.userMobile(step); - } + return GBMinService.userMobile(step); +} /** * Shows the subject menu to the user @@ -675,8 +639,8 @@ export class DialogKeywords { * */ public async showMenu(step) { - return await step.beginDialog('/menu'); - } + return await step.beginDialog('/menu'); +} /** * Performs the transfer of the conversation to a human agent. @@ -685,8 +649,8 @@ export class DialogKeywords { * */ public async transferTo(step, to: string = null) { - return await step.beginDialog('/t', { to: to }); - } + return await step.beginDialog('/t', { to: to }); +} /** * Hears something from user and put it in a variable @@ -695,91 +659,91 @@ export class DialogKeywords { * */ public async hear(step, promise, previousResolve, kind, ...args) { - function random(low, high) { - return Math.random() * (high - low) + low; + function random(low, high) { + return Math.random() * (high - low) + low; + } + const idPromise = random(0, 120000000); + this.min.cbMap[idPromise] = {}; + this.min.cbMap[idPromise].promise = promise; + + let opts = { id: idPromise, previousResolve: previousResolve, kind: kind, args }; + + if (this.hrOn) { + + let sleep = ms => { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); + }; + + // Waits for next message in HEAR delegated context. + + const mobile = await this.userMobile(step); + while (true) { + if (WhatsappDirectLine.state[mobile] === 3) { + break; + } + sleep(5000); } - const idPromise = random(0, 120000000); - this.min.cbMap[idPromise] = {}; - this.min.cbMap[idPromise].promise = promise; + const result = WhatsappDirectLine.lastMessage[this.min.instance.botId + mobile]; + opts = await promise(step, result); - let opts = { id: idPromise, previousResolve: previousResolve, kind: kind, args }; - - if (this.hrOn) { - - let sleep = ms => { - return new Promise(resolve => { - setTimeout(resolve, ms); - }); - }; - - // Waits for next message in HEAR delegated context. - - const mobile = await this.userMobile(step); - while (true) { - if (WhatsappDirectLine.state[mobile] === 3) { - break; - } - sleep(5000); - } - const result = WhatsappDirectLine.lastMessage[this.min.instance.botId + mobile]; - opts = await promise(step, result); - - if (previousResolve !== undefined) { - previousResolve(opts); - } - } - else { - - if (previousResolve !== undefined) { - previousResolve(opts); - } else { - await step.beginDialog('/hear', opts); - } + if (previousResolve !== undefined) { + previousResolve(opts); } } + else { + + if (previousResolve !== undefined) { + previousResolve(opts); + } else { + await step.beginDialog('/hear', opts); + } + } +} /** * Prepares the next dialog to be shown to the specified user. */ public async gotoDialog(step, fromOrDialogName: string, dialogName: string) { - if (dialogName) { - if (dialogName.charAt(0) === '/') { - await step.beginDialog(fromOrDialogName); - } else { - let sec = new SecService(); - let user = await sec.getUserFromSystemId(fromOrDialogName); - if (!user) { - user = await sec.ensureUser(this.min.instance.instanceId, fromOrDialogName, - fromOrDialogName, null, 'whatsapp', 'from', null); - } - await sec.updateUserHearOnDialog(user.userId, dialogName); - } - } - else { + if (dialogName) { + if (dialogName.charAt(0) === '/') { await step.beginDialog(fromOrDialogName); + } else { + let sec = new SecService(); + let user = await sec.getUserFromSystemId(fromOrDialogName); + if (!user) { + user = await sec.ensureUser(this.min.instance.instanceId, fromOrDialogName, + fromOrDialogName, null, 'whatsapp', 'from', null); + } + await sec.updateUserHearOnDialog(user.userId, dialogName); } } + else { + await step.beginDialog(fromOrDialogName); + } +} /** * Talks to the user by using the specified text. */ public async talk(step, text: string) { - await this.min.conversationalService['sendTextWithOptions'](this.min, step, text, - this.user.basicOptions.translatorOn, null); - } + await this.min.conversationalService['sendTextWithOptions'](this.min, step, text, + this.user.basicOptions.translatorOn, null); +} private static getChannel(step): string { - if (!step) return 'whatsapp'; - if (!isNaN(step.context.activity['mobile'])) { - return 'webchat'; - } else { - if (step.context.activity.from && !isNaN(step.context.activity.from.id)) { - return 'whatsapp'; - } - return 'webchat'; + if (!step) return 'whatsapp'; + if (!isNaN(step.context.activity['mobile'])) { + return 'webchat'; + } else { + if (step.context.activity.from && !isNaN(step.context.activity.from.id)) { + return 'whatsapp'; } + return 'webchat'; } +} /** @@ -787,49 +751,49 @@ export class DialogKeywords { */ private async internalSendFile(step, mobile, filename, caption) { - // Handles SEND FILE TO mobile, element in Web Automation. + // Handles SEND FILE TO mobile, element in Web Automation. - const page = filename._page; - if (page) { - const gbaiName = `${this.min.botId}.gbai`; - const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.jpg`); - await filename.screenshot({ path: localName }); + const page = filename._page; + if (page) { + const gbaiName = `${this.min.botId}.gbai`; + const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.jpg`); + await filename.screenshot({ path: localName }); - const url = urlJoin( - GBServer.globals.publicAddress, - this.min.botId, - 'cache', - Path.basename(localName) - ); + const url = urlJoin( + GBServer.globals.publicAddress, + this.min.botId, + 'cache', + Path.basename(localName) + ); - GBLog.info(`BASIC: WebAutomation: Sending the file ${url} to mobile ${mobile}.`); - await this.min.conversationalService.sendFile(this.min, step, mobile, url, caption); - } - - // Handles Markdown. - - else if (filename.indexOf('.md') > -1) { - GBLog.info(`BASIC: Sending the contents of ${filename} markdown to mobile ${mobile}.`); - const md = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename); - if (!md) { - GBLog.info(`BASIC: Markdown file ${filename} not found on database for ${this.min.instance.botId}.`); - } - - await this.min.conversationalService['playMarkdown'](this.min, md, - DialogKeywords.getChannel(step), step, mobile); - - } else { - GBLog.info(`BASIC: Sending the file ${filename} to mobile ${mobile}.`); - const url = urlJoin( - GBServer.globals.publicAddress, - 'kb', - `${this.min.botId}.gbai`, - `${this.min.botId}.gbkb`, - 'assets', - filename - ); - - await this.min.conversationalService.sendFile(this.min, step, mobile, url, caption); - } + GBLog.info(`BASIC: WebAutomation: Sending the file ${url} to mobile ${mobile}.`); + await this.min.conversationalService.sendFile(this.min, step, mobile, url, caption); } + + // Handles Markdown. + + else if (filename.indexOf('.md') > -1) { + GBLog.info(`BASIC: Sending the contents of ${filename} markdown to mobile ${mobile}.`); + const md = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename); + if (!md) { + GBLog.info(`BASIC: Markdown file ${filename} not found on database for ${this.min.instance.botId}.`); + } + + await this.min.conversationalService['playMarkdown'](this.min, md, + DialogKeywords.getChannel(step), step, mobile); + + } else { + GBLog.info(`BASIC: Sending the file ${filename} to mobile ${mobile}.`); + const url = urlJoin( + GBServer.globals.publicAddress, + 'kb', + `${this.min.botId}.gbai`, + `${this.min.botId}.gbkb`, + 'assets', + filename + ); + + await this.min.conversationalService.sendFile(this.min, step, mobile, url, caption); + } +} } \ No newline at end of file diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index d96ebb57..8c36a8f9 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -395,7 +395,7 @@ export class GBVMService extends GBService { }); code = code.replace(/(\w+)\s*\=\s*download\s*(.*),\s*(.*)/gi, ($0, $1, $2, $3) => { - return `${$1} = download (${$2}, ${$3})`; + return `${$1} = sys().download (${$2}, ${$3})`; }); code = code.replace(/(create a bot farm using)(\s)(.*)/gi, ($0, $1, $2, $3) => { @@ -696,9 +696,6 @@ export class GBVMService extends GBService { code = code.replace(/("[^"]*"|'[^']*')|\bclick\b/gi, ($0, $1) => { return $1 === undefined ? 'this.click' : $1; }); - code = code.replace(/("[^"]*"|'[^']*')|\bdownload\b/gi, ($0, $1) => { - return $1 === undefined ? 'this.download' : $1; - }); // await insertion. code = code.replace(/this\./gm, 'await this.'); diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index 3335e027..95a32ae7 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -39,14 +39,19 @@ import { GBDeployer } from '../../core.gbapp/services/GBDeployer'; import { DialogKeywords } from './DialogKeywords'; import { Tabulator } from 'tabulator-tables'; import { GBServer } from '../../../src/app'; +import * as fs from 'fs'; +const Fs = require('fs'); +const Excel = require('exceljs'); const urlJoin = require('url-join'); +const url = require('url'); const puppeteer = require('puppeteer') const Path = require('path'); const sgMail = require('@sendgrid/mail'); const ComputerVisionClient = require('@azure/cognitiveservices-computervision').ComputerVisionClient; const ApiKeyCredentials = require('@azure/ms-rest-js').ApiKeyCredentials; const alasql = require('alasql'); +const DateDiff = require('date-diff'); /** @@ -188,7 +193,7 @@ export class SystemKeywords { * @see puppeteer. */ private async renderTable(data, renderPDF, renderImage) { - if (!data[1]){ + if (!data[1]) { return null; } const gbaiName = `${this.min.botId}.gbai`; @@ -206,13 +211,13 @@ export class SystemKeywords { await page.addStyleTag({ path: 'node_modules/tabulator-tables/dist/css/tabulator_midnight.min.css' }) break; case "blue": - await page.addStyleTag({ path: 'node_modules/tabulator-tables/dist/css/tabulator_modern.min.css' }) - break; - default: + await page.addStyleTag({ path: 'node_modules/tabulator-tables/dist/css/tabulator_modern.min.css' }) + break; + default: break; } - await page.addScriptTag({path: 'node_modules/tabulator-tables/dist/js/tabulator.min.js'}); + await page.addScriptTag({ path: 'node_modules/tabulator-tables/dist/js/tabulator.min.js' }); // Removes internal hidden element used to hold one-based index arrays. @@ -223,9 +228,9 @@ export class SystemKeywords { let fields = []; let keys = Object.keys(data[1]); for (let i = 0; i < keys.length; i++) { - fields.push({field: keys[i], title: keys[i]}); + fields.push({ field: keys[i], title: keys[i] }); } - + // Adds DIV for Tabulator. await page.evaluate(() => { @@ -293,7 +298,7 @@ export class SystemKeywords { public async executeSQL(data, sql, tableName) { const first = data.shift(); data = alasql(sql, [data]); - data.unshift(first); + data.unshift(first); return data; } @@ -569,13 +574,12 @@ export class SystemKeywords { */ public async find(file: string, ...args): Promise { GBLog.info(`BASIC: FIND running on ${file} and args: ${JSON.stringify(args)}...`); - let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min); + const botId = this.min.instance.botId; const path = `/${botId}.gbai/${botId}.gbdata`; - // TODO: if (typeof(file) === + // MAX LINES property. - let document = await this.internalGetDocument(client, baseUrl, path, file); let maxLines = 1000; if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.maxLines) { if (this.dk.user.basicOptions.maxLines.toString().toLowerCase() !== "default") { @@ -583,15 +587,93 @@ export class SystemKeywords { } } - // Creates workbook session that will be discarded. + // Choose data sources based on file type (HTML Table, data variable or sheet file) - let sheets = await client - .api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`) - .get(); + let results; + let header, rows; - let results = await client - .api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:Z${maxLines}')`) - .get(); + if (file['$eval']) { + const container = file['frame'] ? file['frame'] : file['_page']; + const originalSelector = file['originalSelector']; + + // Transforms table + + const resultH = await container.evaluate((originalSelector) => { + const rows = document.querySelectorAll(`${originalSelector} tr`); + return Array.from(rows, row => { + const columns = row.querySelectorAll('th'); + return Array.from(columns, column => column.innerText); + }); + }, originalSelector); + + const result = await container.evaluate((originalSelector) => { + const rows = document.querySelectorAll(`${originalSelector} tr`); + return Array.from(rows, row => { + const columns = row.querySelectorAll('td'); + return Array.from(columns, column => column.innerText); + }); + }, originalSelector); + + header = []; + for (let i = 0; i < resultH[0].length; i++) { + header[i] = resultH[0][i]; + } + + rows = []; + rows[0] = header; + for (let i = 1; i < result.length; i++) { + rows[i] = result[i]; + } + + + } else if (file['cTag']) { + + const gbaiName = `${this.min.botId}.gbai`; + const localName = Path.join('work', gbaiName, 'cache', `csv${GBAdminService.getRndReadableIdentifier()}.csv`); + const url = file['@microsoft.graph.downloadUrl']; + const response = await request({ uri: url, encoding: null }); + Fs.writeFileSync(localName, response, { encoding: null }); + + var workbook = new Excel.Workbook(); + const worksheet = await workbook.csv.readFile(localName); + header = []; + rows = []; + + for (let i = 0; i < worksheet._rows.length; i++) { + const r = worksheet._rows[i]; + let outRow = []; + for (let j = 0; j < r._cells.length; j++) { + outRow.push(r._cells[j].text); + } + + if (i == 0) { + header = outRow; + } + else { + rows.push(outRow); + } + } + + } else { + + let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min); + + let document + document = await this.internalGetDocument(client, baseUrl, path, file); + + // Creates workbook session that will be discarded. + + let sheets = await client + .api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`) + .get(); + + results = await client + .api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:Z${maxLines}')`) + .get(); + + header = results.text[0]; + rows = results.text; + } let getFilter = async (text) => { let filter; @@ -632,7 +714,7 @@ export class SystemKeywords { // Increments columnIndex by looping until find a column match. const filters = []; - const header = results.text[0]; + await CollectionUtil.asyncForEach(args, async arg => { const filter = await getFilter(arg); if (!filter) { @@ -670,12 +752,12 @@ export class SystemKeywords { let foundIndex = 1; // Fills the row variable. - - for (; foundIndex < results.text.length; foundIndex++) { + let rowCount = 0; + for (; foundIndex < rows.length; foundIndex++) { let filterAcceptCount = 0; await CollectionUtil.asyncForEach(filters, async filter => { - let result = results.text[foundIndex][filter.columnIndex]; + let result = rows[foundIndex][filter.columnIndex]; let wholeWord = true; if (this.dk.user && this.dk.user.basicOptions && this.dk.user.basicOptions.wholeWord) { wholeWord = this.dk.user.basicOptions.wholeWord; @@ -760,9 +842,13 @@ export class SystemKeywords { result = result.substr(1); } const resultDate = SystemKeywords.getDateFromLocaleString(result, contentLocale); + if (filter.value['dateOnly']) { + resultDate.setHours(0, 0, 0, 0); + } if (resultDate) { switch (filter.operator) { case '=': + if (resultDate.getTime() == filter.value.getTime()) filterAcceptCount++; break; @@ -789,8 +875,9 @@ export class SystemKeywords { }); if (filterAcceptCount === filters.length) { + rowCount++; let row = {}; - const xlRow = results.text[foundIndex]; + const xlRow = rows[foundIndex]; for (let colIndex = 0; colIndex < xlRow.length; colIndex++) { const propertyName = header[colIndex]; let value = xlRow[colIndex]; @@ -801,7 +888,8 @@ export class SystemKeywords { } row[propertyName] = value; } - row['line'] = foundIndex + 1; + row['line'] = rowCount + 1; + row['originalLine'] = foundIndex + 1; table.push(row); } @@ -814,36 +902,137 @@ export class SystemKeywords { GBLog.info(`BASIC: FIND returned single result: ${table[0]}.`); return table[1]; } else { - GBLog.info(`BASIC: FIND returned multiple results (Count): ${table.length}.`); + GBLog.info(`BASIC: FIND returned multiple results (Count): ${table.length - 1}.`); return table; } } public static getDateFromLocaleString(date: any, contentLocale: any) { + let ret = null; let parts = /^([0-3]?[0-9]).([0-3]?[0-9]).((?:[0-9]{2})?[0-9]{2})\s*(10|11|12|0?[1-9]):([0-5][0-9])/gi.exec(date); if (parts && parts[5]) { + switch (contentLocale) { case 'pt': - return new Date(Number.parseInt(parts[3]), Number.parseInt(parts[2]) - 1, Number.parseInt(parts[1]), + ret = new Date(Number.parseInt(parts[3]), Number.parseInt(parts[2]) - 1, Number.parseInt(parts[1]), Number.parseInt(parts[4]), Number.parseInt(parts[5]), 0, 0); + break; case 'en': - return new Date(Number.parseInt(parts[3]), Number.parseInt(parts[1]) - 1, Number.parseInt(parts[2]), + ret = new Date(Number.parseInt(parts[3]), Number.parseInt(parts[1]) - 1, Number.parseInt(parts[2]), Number.parseInt(parts[4]), Number.parseInt(parts[5]), 0, 0); + break; } + + ret['dateOnly'] = false; } parts = /^([0-3]?[0-9]).([0-3]?[0-9]).((?:[0-9]{2})?[0-9]{2})$/gi.exec(date); if (parts && parts[3]) { + switch (contentLocale) { case 'pt': - return new Date(Number.parseInt(parts[2]), Number.parseInt(parts[1]) - 1, Number.parseInt(parts[3]), 0, 0, 0, 0); + ret = new Date(Number.parseInt(parts[3]), Number.parseInt(parts[2]) - 1, Number.parseInt(parts[1]), 0, 0, 0, 0); + break; case 'en': - return new Date(Number.parseInt(parts[1]), Number.parseInt(parts[2]) - 1, Number.parseInt(parts[3]), 0, 0, 0, 0); + ret = new Date(Number.parseInt(parts[3]), Number.parseInt(parts[1]) - 1, Number.parseInt(parts[2]), 0, 0, 0, 0); + break; } + + ret['dateOnly'] = true; } - return null; + return ret; } + /** + * Performs the download to the .gbdrive Download folder. + * + * @example file = DOWNLOAD element, folder + */ + public async download(element, folder) { + + const page = element['_page']; + const container = element['_frame'] ? element['_frame'] : element['_page']; + + await page.setRequestInterception(true); + await container.click(element.originalSelector); + + const xRequest = await new Promise(resolve => { + page.on('request', interceptedRequest => { + interceptedRequest.abort(); //stop intercepting requests + resolve(interceptedRequest); + }); + }); + + const options = { + encoding: null, + method: xRequest['._method'], + uri: xRequest['_url'], + body: xRequest['_postData'], + headers: xRequest['_headers'] + } + + const cookies = await page.cookies(); + options.headers.Cookie = cookies.map(ck => ck.name + '=' + ck.value).join(';'); + GBLog.info(`BASIC: DOWNLOADING '${options.uri}...'`); + + let local; + let filename; + if (options.uri.indexOf('file://') != -1) { + local = url.fileURLToPath(options.uri); + filename = Path.basename(local); + } + else { + const getBasenameFormUrl = (urlStr) => { + const url = new URL(urlStr) + return Path.basename(url.pathname) + }; + filename = getBasenameFormUrl(options.uri); + } + + let result: Buffer; + if (local) { + result = fs.readFileSync(local); + } else { + result = await request.get(options); + } + let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min); + const botId = this.min.instance.botId; + + // Normalizes all slashes. + + folder = folder.replace(/\\/gi, '/'); + + // Determines full path at source and destination. + + const root = urlJoin(`/${botId}.gbai/${botId}.gbdrive`); + const dstPath = urlJoin(root, folder, filename); + + // Checks if the destination contains subfolders that + // need to be created. + + folder = await this.createFolder(folder); + + // Performs the conversion operation getting a reference + // to the source and calling /content on drive API. + let file; + try { + + file = await client + .api(`${baseUrl}/drive/root:/${dstPath}:/content`) + .put(result); + + } catch (error) { + + if (error.code === "nameAlreadyExists") { + GBLog.info(`BASIC: DOWNLOAD destination file already exists: ${dstPath}.`); + } + throw error; + } + + return file; + } + + /** * Creates a folder in the bot instance drive. * @@ -854,7 +1043,7 @@ export class SystemKeywords { let [baseUrl, client] = await GBDeployer.internalGetDriveClient(this.min); const botId = this.min.instance.botId; - let path = `/${botId}.gbai/${botId}.gbdata`; + let path = `/${botId}.gbai/${botId}.gbdrive`; // Extracts each part of path to call create folder to each // one of them.