2018-04-21 02:59:30 -03:00
|
|
|
/*****************************************************************************\
|
2024-01-09 17:40:48 -03:00
|
|
|
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
|
|
|
|
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
|
|
|
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
|
|
|
|
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
|
|
|
|
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
|
2018-04-21 02:59:30 -03:00
|
|
|
| |
|
2024-08-17 20:30:00 -03:00
|
|
|
| General Bots Copyright (c) pragmatismo.cloud. All rights reserved. |
|
2018-04-21 02:59:30 -03:00
|
|
|
| Licensed under the AGPL-3.0. |
|
2018-11-11 19:09:18 -02:00
|
|
|
| |
|
2018-04-21 02:59:30 -03:00
|
|
|
| According to our dual licensing model, this program can be used either |
|
|
|
|
| under the terms of the GNU Affero General Public License, version 3, |
|
|
|
|
| or under a proprietary license. |
|
|
|
|
| |
|
|
|
|
| The texts of the GNU Affero General Public License with an additional |
|
|
|
|
| permission and of our proprietary license can be found at and |
|
|
|
|
| in the LICENSE file you have received along with this program. |
|
|
|
|
| |
|
|
|
|
| This program is distributed in the hope that it will be useful, |
|
2018-09-11 19:40:53 -03:00
|
|
|
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
|
2018-04-21 02:59:30 -03:00
|
|
|
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
|
|
|
| GNU Affero General Public License for more details. |
|
|
|
|
| |
|
2024-08-17 20:30:00 -03:00
|
|
|
| "General Bots" is a registered trademark of pragmatismo.cloud. |
|
2018-04-21 02:59:30 -03:00
|
|
|
| The licensing of the program under the AGPLv3 does not imply a |
|
|
|
|
| trademark license. Therefore any rights, title and interest in |
|
|
|
|
| our trademarks remain entirely with us. |
|
|
|
|
| |
|
|
|
|
\*****************************************************************************/
|
2018-11-12 12:20:44 -02:00
|
|
|
'use strict';
|
2024-06-27 18:45:33 -03:00
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
import { setFlagsFromString } from 'v8';
|
|
|
|
import { runInNewContext } from 'vm';
|
2024-08-16 10:43:15 -03:00
|
|
|
import { IgApiClient } from 'instagram-private-api';
|
|
|
|
import { readFileSync } from 'fs';
|
|
|
|
import { resolve } from 'path';
|
2024-06-27 18:45:33 -03:00
|
|
|
import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs';
|
2023-12-13 15:33:00 -03:00
|
|
|
import { GBLog, GBMinInstance } from 'botlib';
|
2022-11-18 22:39:14 -03:00
|
|
|
import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js';
|
2021-01-15 08:46:28 -03:00
|
|
|
import { CollectionUtil } from 'pragmatismo-io-framework';
|
2022-11-18 22:39:14 -03:00
|
|
|
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
|
|
|
|
import { GBDeployer } from '../../core.gbapp/services/GBDeployer.js';
|
|
|
|
import { DialogKeywords } from './DialogKeywords.js';
|
|
|
|
import { GBServer } from '../../../src/app.js';
|
|
|
|
import { GBVMService } from './GBVMService.js';
|
|
|
|
import Fs from 'fs';
|
2023-03-06 07:09:24 -03:00
|
|
|
import { GBSSR } from '../../core.gbapp/services/GBSSR.js';
|
2022-11-18 22:39:14 -03:00
|
|
|
import urlJoin from 'url-join';
|
|
|
|
import Excel from 'exceljs';
|
2024-06-27 18:45:33 -03:00
|
|
|
import { BufferWindowMemory } from 'langchain/memory';
|
2024-09-02 20:16:56 -03:00
|
|
|
import csvdb from 'csv-database';
|
2022-11-18 22:39:14 -03:00
|
|
|
import Path from 'path';
|
|
|
|
import ComputerVisionClient from '@azure/cognitiveservices-computervision';
|
|
|
|
import ApiKeyCredentials from '@azure/ms-rest-js';
|
|
|
|
import alasql from 'alasql';
|
|
|
|
import PizZip from 'pizzip';
|
|
|
|
import Docxtemplater from 'docxtemplater';
|
|
|
|
import pptxTemplaterModule from 'pptxtemplater';
|
|
|
|
import _ from 'lodash';
|
2023-07-23 16:37:21 -03:00
|
|
|
import { pdfToPng, PngPageOutput } from 'pdf-to-png-converter';
|
2023-02-06 18:14:48 -03:00
|
|
|
import ImageModule from 'open-docxtemplater-image-module';
|
2023-03-02 07:51:42 -03:00
|
|
|
import { GBConversationalService } from '../../core.gbapp/services/GBConversationalService.js';
|
2023-03-04 16:27:25 -03:00
|
|
|
import { WebAutomationServices } from './WebAutomationServices.js';
|
2023-04-09 19:20:15 -03:00
|
|
|
import { KeywordsExpressions } from './KeywordsExpressions.js';
|
2024-08-23 17:23:22 -03:00
|
|
|
import { ChatServices } from '../../llm.gblib/services/ChatServices.js';
|
2023-08-07 19:12:25 -03:00
|
|
|
import mime from 'mime-types';
|
2023-08-08 09:05:38 -03:00
|
|
|
import exts from '../../../extensions.json' assert { type: 'json' };
|
2023-08-14 09:06:18 -03:00
|
|
|
import { SecService } from '../../security.gbapp/services/SecService.js';
|
2023-10-04 15:21:51 -03:00
|
|
|
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
2023-11-30 15:47:47 -03:00
|
|
|
import retry from 'async-retry';
|
2024-05-23 23:45:45 -03:00
|
|
|
import { BlobServiceClient, BlockBlobClient, StorageSharedKeyCredential } from '@azure/storage-blob';
|
2024-09-04 14:58:11 -03:00
|
|
|
import { FacebookAdsApi, Page } from 'facebook-nodejs-business-sdk';
|
2023-12-12 19:53:05 -03:00
|
|
|
|
|
|
|
import { md5 } from 'js-md5';
|
2024-01-10 15:01:02 -03:00
|
|
|
import { GBUtil } from '../../../src/util.js';
|
2019-02-25 08:36:43 -03:00
|
|
|
|
2019-03-09 16:59:31 -03:00
|
|
|
/**
|
|
|
|
* @fileoverview General Bots server core.
|
|
|
|
*/
|
2020-12-28 18:43:34 -03:00
|
|
|
|
2021-01-15 08:46:28 -03:00
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
* BASIC system class for extra manipulation of bot behaviour.
|
|
|
|
*/
|
2020-12-27 13:30:56 -03:00
|
|
|
export class SystemKeywords {
|
2024-08-16 12:28:05 -03:00
|
|
|
/**
|
|
|
|
* @tags System
|
|
|
|
*/
|
|
|
|
public async callVM({ pid, text }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const step = null;
|
|
|
|
const deployer = null;
|
|
|
|
|
|
|
|
return await GBVMService.callVM(text, min, step, pid, false, [text]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async append({ pid, args }) {
|
2024-08-17 21:35:09 -03:00
|
|
|
if (!args) return [];
|
2024-08-16 12:28:05 -03:00
|
|
|
let array = [].concat(...args);
|
|
|
|
return array.filter(function (item, pos) {
|
|
|
|
return item;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @example SEE CAPTION OF url AS variable
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async seeCaption({ pid, url }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const computerVisionClient = new ComputerVisionClient.ComputerVisionClient(
|
|
|
|
new ApiKeyCredentials.ApiKeyCredentials({ inHeader: { 'Ocp-Apim-Subscription-Key': process.env.VISION_KEY } }),
|
|
|
|
process.env.VISION_ENDPOINT
|
|
|
|
);
|
|
|
|
|
|
|
|
let caption = (await computerVisionClient.describeImage(url)).captions[0];
|
|
|
|
|
|
|
|
const contentLocale = min.core.getParam(
|
|
|
|
min.instance,
|
|
|
|
'Default Content Language',
|
|
|
|
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
|
|
|
|
);
|
|
|
|
GBLogEx.info(min, `GBVision (caption): '${caption.text}' (Confidence: ${caption.confidence.toFixed(2)})`);
|
|
|
|
|
|
|
|
return await min.conversationalService.translate(min, caption.text, contentLocale);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @example SEE TEXT OF url AS variable
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async seeText({ pid, url }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const computerVisionClient = new ComputerVisionClient.ComputerVisionClient(
|
|
|
|
new ApiKeyCredentials.ApiKeyCredentials({ inHeader: { 'Ocp-Apim-Subscription-Key': process.env.VISION_KEY } }),
|
|
|
|
process.env.VISION_ENDPOINT
|
|
|
|
);
|
|
|
|
|
|
|
|
const result = await computerVisionClient.recognizePrintedText(true, url);
|
|
|
|
const text = result.regions[0].lines[0].words[0].text;
|
|
|
|
let final = '';
|
|
|
|
|
|
|
|
for (let i = 0; i < result.regions.length; i++) {
|
|
|
|
const region = result.regions[i];
|
|
|
|
|
|
|
|
for (let j = 0; j < region.lines.length; j++) {
|
|
|
|
const line = region.lines[j];
|
|
|
|
|
|
|
|
for (let k = 0; k < line.words.length; k++) {
|
|
|
|
final += `${line.words[k].text} `;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GBLogEx.info(min, `GBVision (text): '${final}'`);
|
|
|
|
return final;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async sortBy({ pid, array, memberName }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
memberName = memberName.trim();
|
|
|
|
const contentLocale = min.core.getParam(
|
|
|
|
min.instance,
|
|
|
|
'Default Content Language',
|
|
|
|
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
|
|
|
|
);
|
|
|
|
|
|
|
|
// Detects data type from the first element of array.
|
|
|
|
|
|
|
|
let dt = array[0] ? array[0][memberName] : null;
|
|
|
|
let date = SystemKeywords.getDateFromLocaleString(pid, dt, contentLocale);
|
|
|
|
if (date) {
|
|
|
|
return array
|
|
|
|
? array.sort((a, b) => {
|
2024-09-04 14:58:11 -03:00
|
|
|
const c = new Date(a[memberName]);
|
|
|
|
const d = new Date(b[memberName]);
|
|
|
|
return c.getTime() - d.getTime();
|
|
|
|
})
|
2024-08-16 12:28:05 -03:00
|
|
|
: null;
|
|
|
|
} else {
|
|
|
|
return array
|
|
|
|
? array.sort((a, b) => {
|
2024-09-04 14:58:11 -03:00
|
|
|
if (a[memberName] < b[memberName]) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (a[memberName] > b[memberName]) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
})
|
2024-08-16 12:28:05 -03:00
|
|
|
: array;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static JSONAsGBTable(data, headers) {
|
|
|
|
try {
|
|
|
|
let output = [];
|
|
|
|
let isObject = false;
|
|
|
|
if (data[0].gbarray) {
|
|
|
|
return data;
|
|
|
|
} // Already GB Table.
|
|
|
|
if (Array.isArray(data)) {
|
|
|
|
isObject = Object.keys(data[1]) !== null;
|
|
|
|
} else {
|
|
|
|
isObject = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isObject || JSON.parse(data) !== null) {
|
|
|
|
// Copies data from JSON format into simple array.
|
|
|
|
|
|
|
|
if (!Array.isArray(data)) {
|
|
|
|
// If data is a single object, wrap it in an array
|
|
|
|
data = [data];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that keys is an array of strings representing the object keys
|
|
|
|
const keys = Object.keys(data[0]);
|
|
|
|
|
|
|
|
if (headers) {
|
|
|
|
output[0] = [];
|
|
|
|
|
|
|
|
// Copies headers as the first element.
|
|
|
|
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
output[0][i] = keys[i];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
output.push({ gbarray: '0' });
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
|
|
output[i + 1] = [];
|
|
|
|
for (let j = 0; j < keys.length; j++) {
|
|
|
|
output[i + 1][j] = data[i][keys[j]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
GBLog.error(error);
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param data
|
|
|
|
* @param renderImage
|
|
|
|
* @returns
|
|
|
|
*
|
|
|
|
* @see http://tabulator.info/examples/5.2
|
|
|
|
*/
|
|
|
|
private async renderTable(pid, data, renderPDF, renderImage) {
|
|
|
|
if (data.length && !data[1]) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
data = SystemKeywords.JSONAsGBTable(data, true);
|
|
|
|
|
|
|
|
// Detects if it is a collection with repeated
|
|
|
|
// headers.
|
|
|
|
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
const browser = await GBSSR.createBrowser(null);
|
|
|
|
const page = await browser.newPage();
|
|
|
|
await page.minimize();
|
|
|
|
|
|
|
|
// Includes the associated CSS related to current theme.
|
|
|
|
|
|
|
|
const theme: string = await DialogKeywords.getOption({ pid, name: 'theme', root: true });
|
|
|
|
switch (theme) {
|
|
|
|
case 'white':
|
|
|
|
await page.addStyleTag({ path: 'node_modules/tabulator-tables/dist/css/tabulator_simple.min.css' });
|
|
|
|
break;
|
|
|
|
case 'dark':
|
|
|
|
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:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
await page.addScriptTag({ path: 'node_modules/tabulator-tables/dist/js/tabulator.min.js' });
|
|
|
|
|
|
|
|
// Removes internal hidden element used to hold one-based index arrays.
|
|
|
|
|
|
|
|
data.shift();
|
|
|
|
|
|
|
|
// Guess fields from data variable into Tabulator fields collection.
|
|
|
|
|
|
|
|
let fields = [];
|
|
|
|
let keys = Object.keys(data[0]);
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
fields.push({ field: keys[i], title: keys[i] });
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adds DIV for Tabulator.
|
|
|
|
|
|
|
|
await page.evaluate(() => {
|
|
|
|
const el = document.createElement('div');
|
|
|
|
el.setAttribute('id', 'table');
|
|
|
|
document.body.appendChild(el);
|
|
|
|
});
|
|
|
|
|
|
|
|
const code = `
|
|
|
|
var table = new Tabulator("#table", {
|
|
|
|
height:"auto",
|
|
|
|
layout:"fitDataStretch",
|
|
|
|
data: ${JSON.stringify(data)},
|
|
|
|
columns: ${JSON.stringify(fields)}
|
|
|
|
});
|
|
|
|
`;
|
|
|
|
await page.evaluate(code);
|
|
|
|
await page.waitForSelector('#table');
|
|
|
|
|
|
|
|
// Handles image generation.
|
|
|
|
|
|
|
|
let url;
|
|
|
|
let localName;
|
|
|
|
if (renderImage) {
|
|
|
|
localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.png`);
|
|
|
|
await page.screenshot({ path: localName, fullPage: true });
|
|
|
|
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Table image generated at ${url} .`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handles PDF generation.
|
|
|
|
|
|
|
|
if (renderPDF) {
|
|
|
|
localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.pdf`);
|
|
|
|
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
|
|
|
|
let pdf = await page.pdf({ format: 'A4' });
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Table PDF generated at ${url} .`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
await browser.close();
|
|
|
|
return { url, localName };
|
|
|
|
}
|
|
|
|
|
|
|
|
public async closeHandles({ pid }) {
|
2024-08-24 00:12:50 -03:00
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-24 00:13:07 -03:00
|
|
|
const memoryBeforeGC = process.memoryUsage().heapUsed / 1024 / 1024; // in MB
|
2024-08-24 00:22:34 -03:00
|
|
|
|
2024-08-16 12:28:05 -03:00
|
|
|
delete this.cachedMerge[pid];
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-09-04 15:22:59 -03:00
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
// Capture memory usage before GC
|
|
|
|
GBLogEx.info(min, ``);
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
setFlagsFromString('--expose_gc');
|
|
|
|
const gc = runInNewContext('gc'); // nocommit
|
|
|
|
gc();
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
// Capture memory usage after GC
|
|
|
|
const memoryAfterGC = process.memoryUsage().heapUsed / 1024 / 1024; // in MB
|
2024-09-02 20:16:56 -03:00
|
|
|
GBLogEx.info(
|
|
|
|
min,
|
|
|
|
`BASIC: Closing Handles... From ${memoryBeforeGC.toFixed(2)} MB to ${memoryAfterGC.toFixed(2)} MB`
|
|
|
|
);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-08-16 12:28:05 -03:00
|
|
|
public async asPDF({ pid, data }) {
|
|
|
|
let file = await this.renderTable(pid, data, true, false);
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async asImage({ pid, data }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
|
|
|
|
// Checks if it is a GBFILE.
|
|
|
|
|
|
|
|
if (data.data) {
|
|
|
|
const gbfile = data.data;
|
|
|
|
|
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
const tmpDocx = urlJoin(gbaiName, `${botId}.gbdrive`, `tmp${GBAdminService.getRndReadableIdentifier()}.docx`);
|
|
|
|
|
|
|
|
// Performs the conversion operation.
|
|
|
|
|
|
|
|
await client.api(`${baseUrl}/drive/root:/${tmpDocx}:/content`).put(data.data);
|
|
|
|
const res = await client.api(`${baseUrl}/drive/root:/${tmpDocx}:/content?format=pdf`).get();
|
|
|
|
await client.api(`${baseUrl}/drive/root:/${tmpDocx}:/content`).delete();
|
|
|
|
|
|
|
|
const streamToBuffer = stream => {
|
|
|
|
const chunks = [];
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
stream.on('data', chunk => chunks.push(chunk));
|
|
|
|
stream.on('error', reject);
|
|
|
|
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
gbfile.data = await streamToBuffer(res);
|
|
|
|
|
|
|
|
// Converts the PDF to PNG.
|
|
|
|
|
|
|
|
const pngPages: PngPageOutput[] = await pdfToPng(gbfile.data, {
|
|
|
|
disableFontFace: false,
|
|
|
|
useSystemFonts: false,
|
|
|
|
viewportScale: 2.0,
|
|
|
|
pagesToProcess: [1],
|
|
|
|
strictPagesToProcess: false,
|
|
|
|
verbosityLevel: 0
|
|
|
|
});
|
|
|
|
|
|
|
|
// Prepare an image on cache and return the GBFILE information.
|
|
|
|
|
|
|
|
const localName = Path.join('work', gbaiName, 'cache', `img${GBAdminService.getRndReadableIdentifier()}.png`);
|
|
|
|
if (pngPages.length > 0) {
|
|
|
|
const buffer = pngPages[0].content;
|
|
|
|
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
|
|
|
|
|
|
|
|
Fs.writeFileSync(localName, buffer, { encoding: null });
|
|
|
|
|
|
|
|
return { localName: localName, url: url, data: buffer };
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let file = await this.renderTable(pid, data, false, true);
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async executeSQL({ pid, data, sql }) {
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
if (!data || !data[0]) {
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
let objectMode = false;
|
|
|
|
if (data[0].gbarray) {
|
|
|
|
objectMode = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let first;
|
|
|
|
if (objectMode) {
|
|
|
|
first = data.shift();
|
|
|
|
}
|
|
|
|
GBLogEx.info(min, `Executing SQL: ${sql}`);
|
|
|
|
data = alasql(sql, [data]);
|
|
|
|
if (objectMode) {
|
|
|
|
data.unshift(first);
|
|
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrives the content of a given URL.
|
|
|
|
*/
|
|
|
|
public async getFileContents({ pid, url, headers }) {
|
|
|
|
const options = {
|
|
|
|
method: 'GET',
|
|
|
|
encoding: 'binary',
|
|
|
|
headers: headers
|
|
|
|
};
|
|
|
|
return await fetch(url, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrives a random id with a length of five, every time it is called.
|
|
|
|
*/
|
|
|
|
public getRandomId() {
|
|
|
|
const idGeneration = '1v'; // TODO: this.dk['idGeneration'];
|
|
|
|
if (idGeneration && idGeneration.trim().toLowerCase() === 'number') {
|
|
|
|
return GBAdminService.getNumberIdentifier();
|
|
|
|
} else {
|
|
|
|
return GBAdminService.getRndReadableIdentifier().substr(5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrives stock inforation for a given symbol.
|
|
|
|
*/
|
|
|
|
public async getStock({ pid, symbol }) {
|
|
|
|
const url = `http://live-nse.herokuapp.com/?symbol=${symbol}`;
|
|
|
|
let data = await fetch(url);
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Holds script execution for the number of seconds specified.
|
|
|
|
*
|
|
|
|
* @example WAIT 5 ' This will wait five seconds.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async wait({ pid, seconds }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
// tslint:disable-next-line no-string-based-set-timeout
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `WAIT for ${seconds} second(s).`);
|
2024-08-16 12:28:05 -03:00
|
|
|
const timeout = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
await timeout(seconds * 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a text message to the mobile number specified.
|
|
|
|
*
|
|
|
|
* @example TALK TO "+199988887777", "Message text here"
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async talkTo({ pid, mobile, message }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Talking '${message}' to a specific user (${mobile}) (TALK TO). `);
|
2024-08-16 12:28:05 -03:00
|
|
|
await min.conversationalService.sendMarkdownToMobile(min, null, mobile, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a user object from a alias.
|
|
|
|
*
|
|
|
|
* @example user = USER "someone"
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async getUser({ pid, username }) {
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
let sec = new SecService();
|
|
|
|
const user = await sec.getUserFromUsername(min.instance.instanceId, username);
|
|
|
|
|
|
|
|
return { displayName: user.displayName, mobile: user.userSystemId, email: user.email };
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a SMS message to the mobile number specified.
|
|
|
|
*
|
|
|
|
* @example SEND SMS TO "+199988887777", "Message text here"
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async sendSmsTo({ pid, mobile, message }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `SEND SMS TO '${mobile}', message '${message}'.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
await min.conversationalService.sendSms(min, mobile, message);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 1. Defines a cell value in the tabular file.
|
|
|
|
* 2. Defines an element text on HTML page.
|
|
|
|
*
|
|
|
|
* @example SET "file.xlsx", "A2", 4500
|
|
|
|
*
|
|
|
|
* @example SET page, "elementHTMLSelector", "text"
|
|
|
|
|
|
|
|
*/
|
|
|
|
public async set({ pid, handle, file, address, value, name = null }): Promise<any> {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
|
|
|
|
// Handles calls for HTML stuff
|
|
|
|
|
|
|
|
if (handle && WebAutomationServices.isSelector(file)) {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Web automation SET ${file}' to '${address}' . `);
|
2024-08-16 12:28:05 -03:00
|
|
|
await new WebAutomationServices().setElementText({ pid, handle, selector: file, text: address });
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Add a semaphore between FILTER and SET.
|
|
|
|
|
|
|
|
// Processes FILTER option to ensure parallel SET calls.
|
|
|
|
|
|
|
|
const filter = await DialogKeywords.getOption({ pid, name: 'filter' });
|
|
|
|
if (filter) {
|
|
|
|
const row = this.find({ pid, handle: null, args: [filter] });
|
|
|
|
address += row['line'];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handles calls for BASIC persistence on sheet files.
|
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Defining '${address}' in '${file}' to '${value}' (SET). `);
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
|
|
|
|
let document = await this.internalGetDocument(client, baseUrl, path, file);
|
|
|
|
let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
|
|
|
|
let body = { values: [[]] };
|
|
|
|
|
|
|
|
// Processes FILTER option to ensure parallel SET calls.
|
|
|
|
|
|
|
|
let titleAddress;
|
|
|
|
|
|
|
|
if (filter) {
|
|
|
|
// Transforms address number (col index) to letter based.
|
|
|
|
// Eg.: REM This is A column and index automatically specified by filter.
|
|
|
|
// SET file.xlsx, 1, 4000
|
|
|
|
|
|
|
|
if (KeywordsExpressions.isNumber(address)) {
|
|
|
|
address = `${this.numberToLetters(address)}`;
|
|
|
|
titleAddress = `${address}1:${address}1`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Processes SET FILTER directive to calculate address.
|
|
|
|
|
|
|
|
body.values[0][0] = 'id';
|
|
|
|
const addressId = 'A1:A1';
|
|
|
|
await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${addressId}')`
|
|
|
|
)
|
|
|
|
.patch(body);
|
|
|
|
|
|
|
|
const row = await this.find({ pid, handle: null, args: [file, filter] });
|
|
|
|
if (row) {
|
|
|
|
address += row['line']; // Eg.: "A" + 1 = "A1".
|
|
|
|
}
|
|
|
|
}
|
|
|
|
address = address.indexOf(':') !== -1 ? address : address + ':' + address;
|
|
|
|
|
|
|
|
if (titleAddress) {
|
|
|
|
body.values[0][0] = name.trim().replace(/[^a-zA-Z]/gi, '');
|
|
|
|
|
|
|
|
await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${titleAddress}')`
|
|
|
|
)
|
|
|
|
.patch(body);
|
|
|
|
}
|
|
|
|
|
|
|
|
body.values[0][0] = value;
|
|
|
|
await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
|
|
|
|
)
|
|
|
|
.patch(body);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrives a document from the drive, given a path and filename.
|
|
|
|
*/
|
|
|
|
public async internalGetDocument(client: any, baseUrl: any, path: string, file: string) {
|
|
|
|
let res = await client.api(`${baseUrl}/drive/root:/${path}:/children`).get();
|
|
|
|
|
|
|
|
let documents = res.value.filter(m => {
|
|
|
|
return m.name.toLowerCase() === file.toLowerCase();
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!documents || documents.length === 0) {
|
|
|
|
throw new Error(
|
|
|
|
`File '${file}' specified on GBasic command not found. Check the .gbdata or the .gbdialog associated.`,
|
|
|
|
{ cause: 404 }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return documents[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the content of variable into the file in .gbdata default folder.
|
|
|
|
*
|
|
|
|
* @exaple SAVE variable as "my.txt"
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async saveFile({ pid, file, data }): Promise<any> {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Saving '${file}' (SAVE file).`);
|
2024-08-16 12:28:05 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
const path = DialogKeywords.getGBAIPath(min.botId, `gbdrive`);
|
|
|
|
|
|
|
|
// Checks if it is a GB FILE object.
|
|
|
|
|
|
|
|
if (data.data && data.filename) {
|
|
|
|
data = data.data;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
data = GBServer.globals.files[data].data; // TODO
|
|
|
|
await client.api(`${baseUrl}/drive/root:/${path}/${file}:/content`).put(data);
|
|
|
|
} catch (error) {
|
|
|
|
if (error.code === 'itemNotFound') {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `BASIC source file not found: ${file}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
} else if (error.code === 'nameAlreadyExists') {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `BASIC destination file already exists: ${file}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the content of variable into BLOB storage.
|
|
|
|
*
|
|
|
|
* MSFT uses MD5, see https://katelynsills.com/law/the-curious-case-of-md5.
|
|
|
|
*
|
|
|
|
* @exaple UPLOAD file.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async uploadFile({ pid, file }): Promise<any> {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `UPLOAD '${file.name}' ${file.size} bytes.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
// Checks if it is a GB FILE object.
|
|
|
|
|
|
|
|
const accountName = min.core.getParam(min.instance, 'Blob Account');
|
|
|
|
const accountKey = min.core.getParam(min.instance, 'Blob Key');
|
|
|
|
|
|
|
|
const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
|
|
|
|
const baseUrl = `https://${accountName}.blob.core.windows.net`;
|
|
|
|
|
|
|
|
const blobServiceClient = new BlobServiceClient(`${baseUrl}`, sharedKeyCredential);
|
|
|
|
|
|
|
|
// It is an SharePoint object that needs to be downloaded.
|
|
|
|
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
const localName = Path.join('work', gbaiName, 'cache', `${GBAdminService.getRndReadableIdentifier()}.tmp`);
|
|
|
|
const url = file['url'];
|
|
|
|
const response = await fetch(url);
|
|
|
|
|
|
|
|
// Writes it to disk and calculate hash.
|
|
|
|
|
|
|
|
const data = await response.arrayBuffer();
|
|
|
|
Fs.writeFileSync(localName, Buffer.from(data), { encoding: null });
|
|
|
|
const hash = new Uint8Array(md5.array(data));
|
|
|
|
|
|
|
|
// Performs uploading passing local hash.
|
|
|
|
|
|
|
|
const container = blobServiceClient.getContainerClient(accountName);
|
|
|
|
const blockBlobClient: BlockBlobClient = container.getBlockBlobClient(file.path);
|
|
|
|
const res = await blockBlobClient.uploadFile(localName, {
|
|
|
|
blobHTTPHeaders: {
|
|
|
|
blobContentMD5: hash
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// If upload is OK including hash check, removes the temporary file.
|
|
|
|
|
|
|
|
if (res._response.status === 201 && new Uint8Array(res.contentMD5).toString() === hash.toString()) {
|
|
|
|
Fs.rmSync(localName);
|
|
|
|
|
|
|
|
file['md5'] = hash.toString();
|
|
|
|
|
|
|
|
return file;
|
|
|
|
} else {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLog.error(`BLOB HTTP ${res.errorCode} ${res._response.status} .`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Takes note inside a notes.xlsx of .gbdata.
|
|
|
|
*
|
|
|
|
* @example NOTE "text"
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async note({ pid, text }): Promise<any> {
|
|
|
|
await this.save({ pid, file: 'Notes.xlsx', args: [text] });
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves variables to storage, not a worksheet.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async saveToStorageBatch({ pid, table, rows }): Promise<void> {
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-08-16 12:28:05 -03:00
|
|
|
if (rows.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-08-25 13:31:18 -03:00
|
|
|
const t = this.getTableFromName(table, min);
|
2024-08-23 23:36:20 -03:00
|
|
|
let rowsDest = [];
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
rows.forEach(row => {
|
2024-09-04 15:22:59 -03:00
|
|
|
|
2024-08-24 01:10:36 -03:00
|
|
|
if (GBUtil.hasSubObject(row)) {
|
|
|
|
row = this.flattenJSON(row);
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
let dst = {};
|
2024-08-16 12:28:05 -03:00
|
|
|
let i = 0;
|
|
|
|
Object.keys(row).forEach(column => {
|
|
|
|
const field = column.charAt(0).toUpperCase() + column.slice(1);
|
|
|
|
dst[field] = row[column];
|
|
|
|
i++;
|
|
|
|
});
|
|
|
|
rowsDest.push(dst);
|
2024-08-23 23:36:20 -03:00
|
|
|
dst = null;
|
2024-08-24 15:52:23 -03:00
|
|
|
row = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
});
|
2024-08-24 11:35:22 -03:00
|
|
|
GBLogEx.info(min, `SAVE '${table}': ${rows.length} row(s).`);
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-08-16 12:28:05 -03:00
|
|
|
await retry(
|
|
|
|
async bail => {
|
2024-08-25 13:31:18 -03:00
|
|
|
await t.bulkCreate(rowsDest);
|
2024-08-25 13:05:26 -03:00
|
|
|
rowsDest = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
retries: 5,
|
|
|
|
onRetry: err => {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLog.error(`SAVE (retry): ${GBUtil.toYAML(err)}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves variables to storage, not a worksheet.
|
|
|
|
*
|
|
|
|
* @example SAVE "Billing", columnName1, columnName2
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async saveToStorage({ pid, table, fieldsValues, fieldsNames }): Promise<any> {
|
|
|
|
if (!fieldsValues || fieldsValues.length === 0 || !fieldsValues[0]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-24 00:12:50 -03:00
|
|
|
GBLogEx.info(min, `SAVE '${table}': 1 row.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
const definition = this.getTableFromName(table, min);
|
|
|
|
|
|
|
|
// Uppercases fields.
|
2024-08-24 00:50:32 -03:00
|
|
|
|
|
|
|
let dst = {};
|
2024-08-16 12:28:05 -03:00
|
|
|
let i = 0;
|
|
|
|
Object.keys(fieldsValues).forEach(fieldSrc => {
|
|
|
|
const field = fieldsNames[i].charAt(0).toUpperCase() + fieldsNames[i].slice(1);
|
|
|
|
|
|
|
|
dst[field] = fieldsValues[fieldSrc];
|
|
|
|
|
|
|
|
i++;
|
|
|
|
});
|
2024-08-24 15:52:23 -03:00
|
|
|
dst = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
let item;
|
|
|
|
await retry(
|
|
|
|
async bail => {
|
|
|
|
item = await definition.create(dst);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
retries: 5,
|
|
|
|
onRetry: err => {
|
|
|
|
GBLog.error(`Retrying SaveToStorage due to: ${err.message}.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async saveToStorageWithJSON({ pid, table, fieldsValues, fieldsNames }): Promise<any> {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Saving to storage '${table}' (SAVE).`);
|
2024-08-16 12:28:05 -03:00
|
|
|
const minBoot = GBServer.globals.minBoot as any;
|
|
|
|
const definition = minBoot.core.sequelize.models[table];
|
|
|
|
|
|
|
|
let out = [];
|
|
|
|
let data = {},
|
|
|
|
data2 = {};
|
|
|
|
|
|
|
|
// Flattern JSON to a table.
|
|
|
|
|
|
|
|
data = this.flattenJSON(fieldsValues);
|
|
|
|
|
|
|
|
// Uppercases fields.
|
|
|
|
|
|
|
|
Object.keys(data).forEach(field => {
|
|
|
|
const field2 = field.charAt(0).toUpperCase() + field.slice(1);
|
|
|
|
data2[field2] = data[field];
|
|
|
|
});
|
|
|
|
|
|
|
|
return await definition.create(data2);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the content of several variables to a new row in a tabular file.
|
|
|
|
*
|
|
|
|
* @example SAVE "customers.xlsx", name, email, phone, address, city, state, country
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async save({ pid, file, args }): Promise<any> {
|
|
|
|
if (!args) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Saving '${file}' (SAVE). Args: ${args.join(',')}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
|
|
|
|
|
|
|
|
let sheets;
|
|
|
|
let document;
|
|
|
|
try {
|
|
|
|
document = await this.internalGetDocument(client, baseUrl, path, file);
|
|
|
|
sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
|
|
|
|
} catch (e) {
|
|
|
|
if (e.cause === 404) {
|
|
|
|
// Creates the file.
|
|
|
|
|
|
|
|
const blank = Path.join(process.env.PWD, 'blank.xlsx');
|
|
|
|
const data = Fs.readFileSync(blank);
|
|
|
|
await client.api(`${baseUrl}/drive/root:/${path}/${file}:/content`).put(data);
|
|
|
|
|
|
|
|
// Tries to open again.
|
|
|
|
|
|
|
|
document = await this.internalGetDocument(client, baseUrl, path, file);
|
|
|
|
sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let address;
|
|
|
|
let body = { values: [[]] };
|
|
|
|
|
|
|
|
// Processes FILTER option to ensure parallel SET calls.
|
|
|
|
|
|
|
|
const filter = await DialogKeywords.getOption({ pid, name: 'filter' });
|
|
|
|
if (filter) {
|
|
|
|
// Creates id row.
|
|
|
|
|
|
|
|
body.values[0][0] = 'id';
|
|
|
|
const addressId = 'A1:A1';
|
|
|
|
await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${addressId}')`
|
|
|
|
)
|
|
|
|
.patch(body);
|
|
|
|
body.values[0][0] = undefined;
|
|
|
|
|
|
|
|
// FINDs the filtered row to be updated.
|
|
|
|
|
|
|
|
const row = await this.find({ pid, handle: null, args: [file, filter] });
|
|
|
|
if (row) {
|
|
|
|
address = `A${row['line']}:${this.numberToLetters(args.length)}${row['line']}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Editing or saving detection.
|
|
|
|
|
|
|
|
if (!address) {
|
|
|
|
await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A2:DX2')/insert`
|
|
|
|
)
|
|
|
|
.post({});
|
|
|
|
address = `A2:${this.numberToLetters(args.length - 1)}2`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fills rows object to call sheet API.
|
|
|
|
|
|
|
|
for (let index = 0; index < args.length; index++) {
|
|
|
|
let value = args[index];
|
|
|
|
if (value && (await this.isValidDate({ pid, dt: value }))) {
|
|
|
|
value = `'${value}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If filter is defined, skips id column.
|
|
|
|
|
|
|
|
body.values[0][filter ? index + 1 : index] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
await retry(
|
|
|
|
async bail => {
|
|
|
|
const result = await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
|
|
|
|
)
|
|
|
|
.patch(body);
|
|
|
|
|
|
|
|
if (result.status != 200) {
|
|
|
|
GBLogEx.info(min, `Waiting 5 secs. before retrying HTTP ${result.status} GET: ${result.url}`);
|
|
|
|
await GBUtil.sleep(5 * 1000);
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`HTTP:${result.status} retry: ${result.statusText}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
retries: 5,
|
|
|
|
onRetry: err => {
|
|
|
|
GBLog.error(`Retrying HTTP GET due to: ${err.message}.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrives the content of a cell in a tabular file.
|
|
|
|
*
|
|
|
|
* @example value = GET "file.xlsx", "A2"
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async getHttp({ pid, file, addressOrHeaders, httpUsername, httpPs, qs, streaming }): Promise<any> {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
if (file.startsWith('http')) {
|
|
|
|
return await this.getByHttp({
|
|
|
|
pid,
|
|
|
|
url: file,
|
|
|
|
headers: addressOrHeaders,
|
|
|
|
username: httpUsername,
|
|
|
|
ps: httpPs,
|
|
|
|
qs
|
|
|
|
});
|
|
|
|
} else {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `GET '${addressOrHeaders}' in '${file}'.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
('');
|
|
|
|
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
|
|
|
|
|
|
|
|
let 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();
|
|
|
|
|
|
|
|
let results = await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${addressOrHeaders}')`
|
|
|
|
)
|
|
|
|
.get();
|
|
|
|
|
|
|
|
let val = results.text[0][0];
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Getting '${file}' (GET). Value= ${val}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async isValidDate({ pid, dt }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const contentLocale = min.core.getParam(
|
|
|
|
min.instance,
|
|
|
|
'Default Content Language',
|
|
|
|
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
|
|
|
|
);
|
|
|
|
|
|
|
|
let date = SystemKeywords.getDateFromLocaleString(pid, dt, contentLocale);
|
|
|
|
if (!date) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(date instanceof Date)) {
|
|
|
|
date = new Date(date);
|
|
|
|
}
|
|
|
|
|
|
|
|
return !isNaN(date.valueOf());
|
|
|
|
}
|
|
|
|
|
|
|
|
public async isValidNumber({ pid, number }) {
|
|
|
|
return KeywordsExpressions.isNumber(number);
|
|
|
|
}
|
|
|
|
|
|
|
|
public isValidHour({ pid, value }) {
|
|
|
|
return /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static async getFilter(text) {
|
|
|
|
let filter;
|
2024-09-02 20:16:56 -03:00
|
|
|
const operators = [/\<\=/, /\<\>/, /\>\=/, /\</, /\>/,/\blike\b/, /\bnot in\b/, /\bin\b/, /\=/];
|
2024-08-16 12:28:05 -03:00
|
|
|
let done = false;
|
|
|
|
await CollectionUtil.asyncForEach(operators, async op => {
|
|
|
|
var re = new RegExp(op, 'gi');
|
|
|
|
const parts = text.split(re);
|
|
|
|
|
|
|
|
if (parts.length === 2 && !done) {
|
|
|
|
filter = {
|
|
|
|
columnName: parts[0].trim(),
|
|
|
|
operator: op.toString().replace(/\\b/g, '').replace(/\//g, '').replace(/\\/g, '').replace(/\b/g, ''),
|
|
|
|
value: parts[1].trim()
|
|
|
|
};
|
|
|
|
|
|
|
|
// Swaps values and names in case of IN operators.
|
|
|
|
|
|
|
|
if (filter.operator === 'not in' || filter.operator === 'in') {
|
|
|
|
const columnName = filter.columnName;
|
|
|
|
filter.columnName = filter.value;
|
|
|
|
filter.value = columnName;
|
|
|
|
}
|
|
|
|
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return filter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Finds a value or multi-value results in a tabular file.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
*
|
|
|
|
* rows = FIND "file.xlsx", "A2=active", "A2 < 12/06/2010 15:00"
|
|
|
|
* i = 1
|
|
|
|
* do while i <= ubound(row)
|
|
|
|
* row = rows[i]
|
|
|
|
* send sms to "+" + row.mobile, "Hello " + row.name + "! "
|
|
|
|
* loop
|
|
|
|
* @see NPM package data-forge
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
public async find({ pid, handle, args }): Promise<any> {
|
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const file = args[0];
|
|
|
|
args.shift();
|
|
|
|
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
|
|
|
|
|
|
|
|
// MAX LINES property.
|
|
|
|
|
|
|
|
let maxLines = 5000;
|
|
|
|
if (params && params.maxLines) {
|
|
|
|
if (params.maxLines.toString().toLowerCase() !== 'default') {
|
|
|
|
maxLines = Number.parseInt(params.maxLines).valueOf();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
maxLines = maxLines;
|
|
|
|
}
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `FIND running on ${file} (maxLines: ${maxLines}) and args: ${JSON.stringify(args)}...`);
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
// Choose data sources based on file type (HTML Table, data variable or sheet file)
|
|
|
|
|
|
|
|
let results;
|
|
|
|
let header, rows;
|
|
|
|
let page;
|
|
|
|
if (handle) {
|
|
|
|
page = WebAutomationServices.getPageByHandle(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handle && page['$eval'] && WebAutomationServices.isSelector(file)) {
|
|
|
|
const container = page['frame'] ? page['frame'] : page;
|
|
|
|
const originalSelector = file;
|
|
|
|
|
|
|
|
// Transforms table
|
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
let resultH = await container.evaluate(originalSelector => {
|
2024-08-16 12:28:05 -03:00
|
|
|
const rows = document.querySelectorAll(`${originalSelector} tr`);
|
|
|
|
return Array.from(rows, row => {
|
|
|
|
const columns = row.querySelectorAll('th');
|
|
|
|
return Array.from(columns, column => column.innerText);
|
|
|
|
});
|
|
|
|
}, originalSelector);
|
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
let result = await container.evaluate(originalSelector => {
|
2024-08-16 12:28:05 -03:00
|
|
|
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];
|
|
|
|
}
|
2024-08-23 23:36:20 -03:00
|
|
|
resultH = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
rows = [];
|
|
|
|
rows[0] = header;
|
|
|
|
for (let i = 1; i < result.length; i++) {
|
|
|
|
rows[i] = result[i];
|
|
|
|
}
|
2024-08-23 23:36:20 -03:00
|
|
|
result = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
} else if (file['cTag']) {
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
const localName = Path.join('work', gbaiName, 'cache', `csv${GBAdminService.getRndReadableIdentifier()}.csv`);
|
|
|
|
const url = file['@microsoft.graph.downloadUrl'];
|
|
|
|
const response = await fetch(url);
|
|
|
|
Fs.writeFileSync(localName, Buffer.from(await response.arrayBuffer()), { encoding: null });
|
|
|
|
|
|
|
|
var workbook = new Excel.Workbook();
|
2024-08-23 23:36:20 -03:00
|
|
|
let worksheet = await workbook.csv.readFile(localName);
|
2024-08-16 12:28:05 -03:00
|
|
|
header = [];
|
|
|
|
rows = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < worksheet.rowCount; i++) {
|
|
|
|
const r = worksheet.getRow(i + 1);
|
|
|
|
let outRow = [];
|
|
|
|
let hasValue = false;
|
|
|
|
for (let j = 0; j < r.cellCount; j++) {
|
|
|
|
const value = r.getCell(j + 1).text;
|
|
|
|
if (value) {
|
|
|
|
hasValue = true;
|
|
|
|
}
|
|
|
|
outRow.push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
header = outRow;
|
|
|
|
} else if (hasValue) {
|
|
|
|
rows.push(outRow);
|
|
|
|
}
|
|
|
|
}
|
2024-08-23 23:36:20 -03:00
|
|
|
worksheet = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
} else if (file.indexOf('.xlsx') !== -1) {
|
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(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:CZ${maxLines}')`
|
|
|
|
)
|
|
|
|
.get();
|
|
|
|
|
|
|
|
header = results.text[0];
|
|
|
|
rows = results.text;
|
2024-09-02 20:16:56 -03:00
|
|
|
} else if (file.indexOf('.csv') !== -1) {
|
|
|
|
let res;
|
|
|
|
let path = DialogKeywords.getGBAIPath(min.botId, `gbdata`);
|
|
|
|
const csvFile = Path.join(GBConfigService.get('STORAGE_LIBRARY'), path, file);
|
|
|
|
const firstLine = Fs.readFileSync(csvFile, 'utf8').split('\n')[0];
|
|
|
|
const headers = firstLine.split(',');
|
|
|
|
const db = await csvdb(csvFile, headers, ',');
|
|
|
|
if (args[0]) {
|
|
|
|
const systemFilter = await SystemKeywords.getFilter(args[0]);
|
|
|
|
let filter = {};
|
|
|
|
filter[systemFilter.columnName] = systemFilter.value;
|
|
|
|
res = await db.get(filter);
|
|
|
|
} else {
|
|
|
|
res = await db.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.length > 1 ? res : res[0];
|
2024-08-16 12:28:05 -03:00
|
|
|
} else {
|
|
|
|
const t = this.getTableFromName(file, min);
|
|
|
|
|
|
|
|
if (!t) {
|
|
|
|
throw new Error(`TABLE ${file} not found. Check TABLE keywords.`);
|
|
|
|
}
|
2024-08-16 14:04:17 -03:00
|
|
|
let res;
|
|
|
|
if (args[0]) {
|
|
|
|
const systemFilter = await SystemKeywords.getFilter(args[0]);
|
|
|
|
let filter = {};
|
|
|
|
filter[systemFilter.columnName] = systemFilter.value;
|
|
|
|
res = await t.findAll({ where: filter });
|
|
|
|
} else {
|
|
|
|
res = await t.findAll();
|
|
|
|
}
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
return res.length > 1 ? res : res[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
const contentLocale = min.core.getParam(
|
|
|
|
min.instance,
|
|
|
|
'Default Content Language',
|
|
|
|
GBConfigService.get('DEFAULT_CONTENT_LANGUAGE')
|
|
|
|
);
|
|
|
|
|
|
|
|
// Increments columnIndex by looping until find a column match.
|
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
let filters = [];
|
2024-08-16 12:28:05 -03:00
|
|
|
let predefinedFilterTypes;
|
|
|
|
if (params.filterTypes) {
|
|
|
|
predefinedFilterTypes = params.filterTypes.split(',');
|
|
|
|
}
|
|
|
|
|
|
|
|
let filterIndex = 0;
|
|
|
|
await CollectionUtil.asyncForEach(args, async arg => {
|
|
|
|
const filter = await SystemKeywords.getFilter(arg);
|
|
|
|
if (!filter) {
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`FIND filter has an error: ${arg} check this and publish .gbdialog again.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
let columnIndex = 0;
|
|
|
|
for (; columnIndex < header.length; columnIndex++) {
|
|
|
|
if (header[columnIndex].toLowerCase() === filter.columnName.toLowerCase()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
filter.columnIndex = columnIndex;
|
|
|
|
const fixed = predefinedFilterTypes ? predefinedFilterTypes[filterIndex] : null;
|
|
|
|
|
|
|
|
if (this.isValidHour(filter.value)) {
|
|
|
|
filter.dataType = fixed ? fixed : 'hourInterval';
|
|
|
|
} else if (await this.isValidDate({ pid, dt: filter.value })) {
|
|
|
|
filter.value = SystemKeywords.getDateFromLocaleString(pid, filter.value, contentLocale);
|
|
|
|
filter.dataType = fixed ? fixed : 'date';
|
|
|
|
} else if (await this.isValidNumber({ pid, number: filter.value })) {
|
|
|
|
filter.value = Number.parseInt(filter.value);
|
|
|
|
filter.dataType = fixed ? fixed : 'number';
|
|
|
|
} else {
|
|
|
|
filter.value = filter.value;
|
|
|
|
filter.dataType = fixed ? fixed : 'string';
|
|
|
|
}
|
|
|
|
filters.push(filter);
|
|
|
|
filterIndex++;
|
|
|
|
});
|
|
|
|
|
|
|
|
// As BASIC uses arrays starting with 1 (one) as index,
|
|
|
|
// a ghost element is added at 0 (zero) position.
|
|
|
|
|
|
|
|
let table = [];
|
|
|
|
table.push({ gbarray: '0' });
|
|
|
|
let foundIndex = 1;
|
|
|
|
|
|
|
|
// Fills the row variable.
|
|
|
|
|
|
|
|
let rowCount = 0;
|
|
|
|
for (; foundIndex < rows.length; foundIndex++) {
|
|
|
|
let filterAcceptCount = 0;
|
|
|
|
await CollectionUtil.asyncForEach(filters, async filter => {
|
|
|
|
let result = rows[foundIndex][filter.columnIndex];
|
|
|
|
let wholeWord = true;
|
|
|
|
if (user && params && params.wholeWord) {
|
|
|
|
wholeWord = params.wholeWord;
|
|
|
|
}
|
|
|
|
if (!result) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (filter.dataType) {
|
|
|
|
case 'string':
|
|
|
|
const v1 = GBConversationalService.removeDiacritics(result.toLowerCase().trim());
|
|
|
|
const v2 = GBConversationalService.removeDiacritics(filter.value.toLowerCase().trim());
|
|
|
|
GBLogEx.info(min, `FIND filter: ${v1} ${filter.operator} ${v2}.`);
|
|
|
|
|
|
|
|
switch (filter.operator) {
|
|
|
|
case '=':
|
|
|
|
if (v1 === v2) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case '<>':
|
|
|
|
if (v1 !== v2) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'not in':
|
|
|
|
if (v1.indexOf(v2) === -1) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'in':
|
|
|
|
if (wholeWord) {
|
|
|
|
if (v1 === v2) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (v1.indexOf(v2) > -1) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'number':
|
|
|
|
switch (filter.operator) {
|
|
|
|
case '=':
|
|
|
|
if (Number.parseInt(result) === filter.value) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'hourInterval':
|
|
|
|
switch (filter.operator) {
|
|
|
|
case '=':
|
|
|
|
if (v1 === v2) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'in':
|
|
|
|
const e = result.split(';');
|
|
|
|
const hr = Number.parseInt(filter.value.split(':')[0]);
|
|
|
|
let lastHour = Number.parseInt(e[0]);
|
|
|
|
let found = false;
|
|
|
|
await CollectionUtil.asyncForEach(e, async hour => {
|
|
|
|
if (!found && lastHour <= hr && hr <= hour) {
|
|
|
|
filterAcceptCount++;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
lastHour = hour;
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'date':
|
|
|
|
if (result.charAt(0) === "'") {
|
|
|
|
result = result.substr(1);
|
|
|
|
}
|
|
|
|
const resultDate = SystemKeywords.getDateFromLocaleString(pid, result, contentLocale);
|
|
|
|
if (resultDate) {
|
|
|
|
if (filter.value['dateOnly']) {
|
|
|
|
resultDate.setHours(0, 0, 0, 0);
|
|
|
|
}
|
|
|
|
switch (filter.operator) {
|
|
|
|
case '=':
|
|
|
|
if (resultDate.getTime() == filter.value.getTime()) filterAcceptCount++;
|
|
|
|
break;
|
|
|
|
case '<':
|
|
|
|
if (resultDate.getTime() < filter.value.getTime()) filterAcceptCount++;
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
if (resultDate.getTime() > filter.value.getTime()) filterAcceptCount++;
|
|
|
|
break;
|
|
|
|
case '<=':
|
|
|
|
if (resultDate.getTime() <= filter.value.getTime()) filterAcceptCount++;
|
|
|
|
break;
|
|
|
|
case '>=':
|
|
|
|
if (resultDate.getTime() >= filter.value.getTime()) filterAcceptCount++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (filterAcceptCount === filters.length) {
|
|
|
|
rowCount++;
|
|
|
|
let row = {};
|
2024-08-24 15:52:23 -03:00
|
|
|
let xlRow = rows[foundIndex];
|
2024-08-16 12:28:05 -03:00
|
|
|
let hasValue = false;
|
|
|
|
for (let colIndex = 0; colIndex < xlRow.length; colIndex++) {
|
|
|
|
const propertyName = header[colIndex].trim();
|
|
|
|
|
|
|
|
let value = xlRow[colIndex];
|
|
|
|
if (value) {
|
|
|
|
hasValue = true;
|
|
|
|
value = value.trim();
|
|
|
|
if (value.charAt(0) === "'") {
|
|
|
|
if (await this.isValidDate({ pid, dt: value.substr(1) })) {
|
|
|
|
value = value.substr(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
row[propertyName] = value;
|
2024-08-24 15:52:23 -03:00
|
|
|
value = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
2024-08-24 15:52:23 -03:00
|
|
|
xlRow = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
row['ordinal'] = rowCount;
|
|
|
|
row['line'] = foundIndex + 1;
|
|
|
|
if (hasValue) {
|
|
|
|
table.push(row);
|
|
|
|
}
|
2024-08-24 15:52:23 -03:00
|
|
|
row = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const outputArray = await DialogKeywords.getOption({ pid, name: 'output' });
|
2024-08-23 23:36:20 -03:00
|
|
|
filters = null;
|
|
|
|
header = null;
|
|
|
|
rows = null;
|
2024-08-16 12:28:05 -03:00
|
|
|
|
|
|
|
if (table.length === 1) {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `FIND returned no results (zero rows).`);
|
2024-08-16 12:28:05 -03:00
|
|
|
return null;
|
|
|
|
} else if (table.length === 2 && !outputArray) {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `FIND returned single result: ${table[0]}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
return table[1];
|
|
|
|
} else {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `FIND returned multiple results (Count): ${table.length - 1}.`);
|
2024-08-16 12:28:05 -03:00
|
|
|
return table;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static getDateFromLocaleString(pid, 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':
|
|
|
|
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':
|
|
|
|
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':
|
|
|
|
ret = new Date(
|
|
|
|
Number.parseInt(parts[3]),
|
|
|
|
Number.parseInt(parts[2]) - 1,
|
|
|
|
Number.parseInt(parts[1]),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 'en':
|
|
|
|
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 ret;
|
|
|
|
}
|
2021-08-15 10:13:36 -03:00
|
|
|
|
2024-03-15 07:14:21 -03:00
|
|
|
public async setSystemPrompt({ pid, text }) {
|
2024-03-11 13:30:11 -03:00
|
|
|
let { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2024-03-15 07:14:21 -03:00
|
|
|
|
2024-03-11 13:30:11 -03:00
|
|
|
if (user) {
|
2024-03-15 07:14:21 -03:00
|
|
|
ChatServices.userSystemPrompt[user.userSystemId] = text;
|
|
|
|
|
2024-03-11 13:30:11 -03:00
|
|
|
const path = DialogKeywords.getGBAIPath(min.botId);
|
2024-05-23 23:45:45 -03:00
|
|
|
const systemPromptFile = urlJoin(process.cwd(), 'work', path, 'users', user.userSystemId, 'systemPrompt.txt');
|
2024-03-15 07:14:21 -03:00
|
|
|
Fs.writeFileSync(systemPromptFile, text);
|
2024-03-11 13:30:11 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-13 10:02:49 -03:00
|
|
|
/**
|
2020-12-28 09:27:35 -03:00
|
|
|
* Creates a folder in the bot instance drive.
|
2020-12-27 13:30:56 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* @example folder = CREATE FOLDER "notes\01"
|
2020-12-27 13:30:56 -03:00
|
|
|
*
|
2020-12-13 10:02:49 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async createFolder({ pid, name }) {
|
2023-03-04 16:27:25 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
2023-03-09 18:56:55 -03:00
|
|
|
let path = DialogKeywords.getGBAIPath(min.botId, `gbdrive`);
|
2023-03-09 17:46:34 -03:00
|
|
|
|
2021-01-15 08:46:28 -03:00
|
|
|
// Extracts each part of path to call create folder to each
|
|
|
|
// one of them.
|
|
|
|
|
|
|
|
name = name.replace(/\\/gi, '/');
|
|
|
|
const parts = name.split('/');
|
|
|
|
let lastFolder = null;
|
|
|
|
|
2021-01-15 11:48:18 -03:00
|
|
|
// Creates each subfolder.
|
|
|
|
|
2021-01-15 08:46:28 -03:00
|
|
|
await CollectionUtil.asyncForEach(parts, async item => {
|
2021-01-15 11:48:18 -03:00
|
|
|
// Calls drive API.
|
2020-12-13 10:02:49 -03:00
|
|
|
|
2020-12-14 09:28:12 -03:00
|
|
|
const body = {
|
2022-11-19 23:34:58 -03:00
|
|
|
name: item,
|
|
|
|
folder: {},
|
|
|
|
'@microsoft.graph.conflictBehavior': 'fail'
|
2020-12-27 13:30:56 -03:00
|
|
|
};
|
2021-01-15 11:48:18 -03:00
|
|
|
|
|
|
|
try {
|
2022-11-19 23:34:58 -03:00
|
|
|
lastFolder = await client.api(`${baseUrl}/drive/root:/${path}:/children`).post(body);
|
2021-01-15 11:48:18 -03:00
|
|
|
} catch (error) {
|
2022-11-19 23:34:58 -03:00
|
|
|
if (error.code !== 'nameAlreadyExists') {
|
2021-01-15 11:48:18 -03:00
|
|
|
throw error;
|
2022-11-19 23:34:58 -03:00
|
|
|
} else {
|
|
|
|
lastFolder = await client.api(`${baseUrl}/drive/root:/${urlJoin(path, item)}`).get();
|
2021-01-15 11:48:18 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Increments path to the next child be created.
|
|
|
|
|
|
|
|
path = urlJoin(path, item);
|
2020-12-14 09:28:12 -03:00
|
|
|
});
|
2021-01-15 08:46:28 -03:00
|
|
|
return lastFolder;
|
2020-12-13 10:02:49 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-12-28 09:27:35 -03:00
|
|
|
* Shares a folder from the drive to a e-mail recipient.
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* @example
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-13 10:02:49 -03:00
|
|
|
* folder = CREATE FOLDER "notes\10"
|
|
|
|
* SHARE FOLDER folder, "nome@domain.com", "E-mail message"
|
2020-12-27 13:30:56 -03:00
|
|
|
*
|
2020-12-13 10:02:49 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async shareFolder({ pid, folder, email, message }) {
|
2023-03-04 16:27:25 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
2023-04-01 10:42:44 -03:00
|
|
|
const path = DialogKeywords.getGBAIPath(min.botId, `gbdrive`);
|
2023-03-09 17:46:34 -03:00
|
|
|
const root = urlJoin(path, folder);
|
2022-11-06 20:19:05 -03:00
|
|
|
|
2022-11-19 23:34:58 -03:00
|
|
|
const src = await client.api(`${baseUrl}/drive/root:/${root}`).get();
|
2022-11-05 17:59:41 -03:00
|
|
|
|
2022-11-06 20:19:05 -03:00
|
|
|
const driveId = src.parentReference.driveId;
|
|
|
|
const itemId = src.id;
|
2021-01-15 11:48:18 -03:00
|
|
|
const body = {
|
2022-11-19 23:34:58 -03:00
|
|
|
recipients: [{ email: email }],
|
|
|
|
message: message,
|
|
|
|
requireSignIn: true,
|
|
|
|
sendInvitation: true,
|
|
|
|
roles: ['write']
|
2021-01-15 11:48:18 -03:00
|
|
|
};
|
2020-12-13 10:02:49 -03:00
|
|
|
|
2022-11-19 23:34:58 -03:00
|
|
|
await client.api(`https://graph.microsoft.com/v1.0/drives/${driveId}/items/${itemId}/invite`).post(body);
|
2020-12-13 10:02:49 -03:00
|
|
|
}
|
|
|
|
|
2023-04-09 19:20:15 -03:00
|
|
|
public async internalCreateDocument(min, path, content) {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `CREATE DOCUMENT '${path}...'`);
|
2023-04-09 19:20:15 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
const tmpDocx = urlJoin(gbaiName, path);
|
|
|
|
|
|
|
|
// Templates a blank {content} tag inside the blank.docx.
|
|
|
|
|
|
|
|
const blank = Path.join(process.env.PWD, 'blank.docx');
|
|
|
|
let buf = Fs.readFileSync(blank);
|
|
|
|
let zip = new PizZip(buf);
|
|
|
|
let doc = new Docxtemplater();
|
|
|
|
doc.setOptions({ linebreaks: true });
|
|
|
|
doc.loadZip(zip);
|
|
|
|
doc.setData({ content: content }).render();
|
|
|
|
buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' });
|
|
|
|
|
|
|
|
// Performs the upload.
|
|
|
|
|
|
|
|
await client.api(`${baseUrl}/drive/root:/${tmpDocx}:/content`).put(buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createDocument({ pid, path, content }) {
|
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
this.internalCreateDocument(min, path, content);
|
|
|
|
}
|
|
|
|
|
2020-12-28 09:27:35 -03:00
|
|
|
/**
|
|
|
|
* Copies a drive file from a place to another .
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* @example
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* COPY "template.xlsx", "reports\" + customerName + "\final.xlsx"
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async copyFile({ pid, src, dest }) {
|
2023-03-04 16:27:25 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `BEGINING COPY '${src}' to '${dest}'`);
|
2023-03-04 16:27:25 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
2021-01-15 11:48:18 -03:00
|
|
|
|
|
|
|
// Normalizes all slashes.
|
|
|
|
|
|
|
|
src = src.replace(/\\/gi, '/');
|
|
|
|
dest = dest.replace(/\\/gi, '/');
|
|
|
|
|
|
|
|
// Determines full path at source and destination.
|
|
|
|
|
2023-03-09 17:46:34 -03:00
|
|
|
const root = DialogKeywords.getGBAIPath(botId, 'gbdrive');
|
2021-01-15 08:46:28 -03:00
|
|
|
const srcPath = urlJoin(root, src);
|
2023-03-09 09:49:37 -03:00
|
|
|
const dstPath = urlJoin(root, dest);
|
2021-01-15 11:48:18 -03:00
|
|
|
|
|
|
|
// Checks if the destination contains subfolders that
|
|
|
|
// need to be created.
|
|
|
|
|
2021-01-15 08:46:28 -03:00
|
|
|
let folder;
|
2021-01-15 11:48:18 -03:00
|
|
|
if (dest.indexOf('/') !== -1) {
|
2022-06-08 12:28:06 -03:00
|
|
|
const pathOnly = Path.dirname(dest);
|
2023-01-26 12:47:37 -03:00
|
|
|
folder = await this.createFolder({ pid, name: pathOnly });
|
2022-11-19 23:34:58 -03:00
|
|
|
} else {
|
|
|
|
folder = await client.api(`${baseUrl}/drive/root:/${root}`).get();
|
2021-01-15 08:46:28 -03:00
|
|
|
}
|
|
|
|
|
2021-01-15 11:48:18 -03:00
|
|
|
// Performs the copy operation getting a reference
|
|
|
|
// to the source and calling /copy on drive API.
|
|
|
|
|
2021-01-15 08:46:28 -03:00
|
|
|
try {
|
2022-11-19 23:34:58 -03:00
|
|
|
const srcFile = await client.api(`${baseUrl}/drive/root:/${srcPath}`).get();
|
2021-01-15 11:48:18 -03:00
|
|
|
const destFile = {
|
2022-11-19 23:34:58 -03:00
|
|
|
parentReference: { driveId: folder.parentReference.driveId, id: folder.id },
|
|
|
|
name: `${Path.basename(dest)}`
|
|
|
|
};
|
2023-04-09 19:20:15 -03:00
|
|
|
const file = await client.api(`${baseUrl}/drive/items/${srcFile.id}/copy`).post(destFile);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `FINISHED COPY '${src}' to '${dest}'`);
|
2023-04-09 19:20:15 -03:00
|
|
|
return file;
|
2021-01-15 08:46:28 -03:00
|
|
|
} catch (error) {
|
2022-11-19 23:34:58 -03:00
|
|
|
if (error.code === 'itemNotFound') {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `COPY source file not found: ${srcPath}.`);
|
2022-11-19 23:34:58 -03:00
|
|
|
} else if (error.code === 'nameAlreadyExists') {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `COPY destination file already exists: ${dstPath}.`);
|
2021-01-15 08:46:28 -03:00
|
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
2020-11-08 13:39:18 -03:00
|
|
|
}
|
|
|
|
|
2021-01-15 19:21:27 -03:00
|
|
|
/**
|
|
|
|
* Converts a drive file from a place to another .
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
|
|
|
* Supported sources csv, doc, docx, odp, ods, odt, pot, potm, potx, pps,
|
2021-01-15 19:21:27 -03:00
|
|
|
* ppsx, ppsxm, ppt, pptm, pptx, rtf, xls, xlsx
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2021-01-15 19:21:27 -03:00
|
|
|
* @example
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2021-01-15 19:21:27 -03:00
|
|
|
* CONVERT "customers.xlsx" TO "reports\" + today + ".pdf"
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2021-01-15 19:21:27 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async convert({ pid, src, dest }) {
|
2023-03-04 16:27:25 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `CONVERT '${src}' to '${dest}'`);
|
2023-03-04 16:27:25 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const botId = min.instance.botId;
|
2021-01-15 19:21:27 -03:00
|
|
|
|
|
|
|
// Normalizes all slashes.
|
|
|
|
|
|
|
|
src = src.replace(/\\/gi, '/');
|
|
|
|
dest = dest.replace(/\\/gi, '/');
|
|
|
|
|
|
|
|
// Determines full path at source and destination.
|
2023-04-01 10:42:44 -03:00
|
|
|
const path = DialogKeywords.getGBAIPath(min.botId, `gbdrive`);
|
2023-03-09 17:46:34 -03:00
|
|
|
const root = path;
|
2021-01-15 19:21:27 -03:00
|
|
|
const srcPath = urlJoin(root, src);
|
2023-03-09 18:56:55 -03:00
|
|
|
const dstPath = urlJoin(path, dest);
|
2021-01-15 19:21:27 -03:00
|
|
|
|
|
|
|
// Checks if the destination contains subfolders that
|
|
|
|
// need to be created.
|
|
|
|
|
2021-02-28 21:04:31 -03:00
|
|
|
let folder;
|
2021-01-15 19:21:27 -03:00
|
|
|
if (dest.indexOf('/') !== -1) {
|
2022-06-08 12:28:06 -03:00
|
|
|
const pathOnly = Path.dirname(dest);
|
2023-01-19 10:39:37 -03:00
|
|
|
folder = await this.createFolder({ pid, name: pathOnly });
|
2022-11-19 23:34:58 -03:00
|
|
|
} else {
|
|
|
|
folder = await client.api(`${baseUrl}/drive/root:/${root}`).get();
|
2021-01-15 19:21:27 -03:00
|
|
|
}
|
2021-03-09 14:06:19 -03:00
|
|
|
|
2021-01-15 19:21:27 -03:00
|
|
|
// Performs the conversion operation getting a reference
|
|
|
|
// to the source and calling /content on drive API.
|
|
|
|
|
|
|
|
try {
|
2022-11-19 23:34:58 -03:00
|
|
|
const res = await client.api(`${baseUrl}/drive/root:/${srcPath}:/content?format=pdf`).get();
|
2021-01-15 19:21:27 -03:00
|
|
|
|
2023-02-05 18:19:39 -03:00
|
|
|
const streamToBuffer = stream => {
|
2022-11-19 23:34:58 -03:00
|
|
|
const chunks = [];
|
2021-01-15 19:21:27 -03:00
|
|
|
return new Promise((resolve, reject) => {
|
2022-11-19 23:34:58 -03:00
|
|
|
stream.on('data', chunk => chunks.push(chunk));
|
|
|
|
stream.on('error', reject);
|
|
|
|
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
|
|
});
|
|
|
|
};
|
2021-01-15 19:21:27 -03:00
|
|
|
|
2023-02-05 18:19:39 -03:00
|
|
|
const result = await streamToBuffer(res);
|
2021-01-15 19:21:27 -03:00
|
|
|
|
2022-11-19 23:34:58 -03:00
|
|
|
await client.api(`${baseUrl}/drive/root:/${dstPath}:/content`).put(result);
|
2021-01-15 19:21:27 -03:00
|
|
|
} catch (error) {
|
2022-11-19 23:34:58 -03:00
|
|
|
if (error.code === 'itemNotFound') {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `CONVERT source file not found: ${srcPath}.`);
|
2022-11-19 23:34:58 -03:00
|
|
|
} else if (error.code === 'nameAlreadyExists') {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `CONVERT destination file already exists: ${dstPath}.`);
|
2021-01-15 19:21:27 -03:00
|
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-19 23:34:58 -03:00
|
|
|
/**
|
2020-12-28 09:27:35 -03:00
|
|
|
* Generate a secure and unique password.
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* @example pass = PASSWORD
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public generatePassword(pid) {
|
2019-02-23 13:17:21 -03:00
|
|
|
return GBAdminService.getRndPassword();
|
|
|
|
}
|
2023-10-07 18:40:10 -03:00
|
|
|
|
2024-01-06 22:21:11 -03:00
|
|
|
private flattenJSON(obj, res = {}, separator = '_', parent = null) {
|
2023-10-07 18:40:10 -03:00
|
|
|
for (let key in obj) {
|
2024-09-02 20:16:56 -03:00
|
|
|
if (!obj.hasOwnProperty(key) || typeof obj[key] === 'function') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (typeof obj[key] !== 'object' || obj[key] instanceof Date) {
|
|
|
|
// If not defined already, add the flattened field.
|
|
|
|
const newKey = `${parent ? parent + separator : ''}${key}`;
|
|
|
|
if (!res.hasOwnProperty(newKey)) {
|
|
|
|
res[newKey] = obj[key];
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
2024-09-02 20:16:56 -03:00
|
|
|
GBLog.verbose(`Ignoring duplicated field in flatten operation to storage: ${key}.`);
|
2023-10-21 14:47:30 -03:00
|
|
|
}
|
2024-09-02 20:16:56 -03:00
|
|
|
} else {
|
|
|
|
// Create a temporary reference to the nested object to prevent memory leaks.
|
|
|
|
const tempObj = obj[key];
|
|
|
|
this.flattenJSON(tempObj, res, separator, `${parent ? parent + separator : ''}${key}`);
|
|
|
|
// Clear the reference to avoid holding unnecessary objects in memory.
|
|
|
|
obj[key] = null;
|
|
|
|
}
|
2024-05-23 23:45:45 -03:00
|
|
|
}
|
2023-10-07 18:40:10 -03:00
|
|
|
return res;
|
2024-09-02 20:16:56 -03:00
|
|
|
}
|
2023-10-07 18:40:10 -03:00
|
|
|
|
2023-12-25 18:42:23 -03:00
|
|
|
public async getCustomToken({ pid, tokenName }) {
|
2023-12-10 21:08:27 -03:00
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2023-12-25 18:42:23 -03:00
|
|
|
GBLogEx.info(min, `BASIC internal getCustomToken: ${tokenName}`);
|
2023-12-10 12:21:28 -03:00
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
const token = await (min.adminService as any)['acquireElevatedToken'](
|
|
|
|
min.instance.instanceId,
|
|
|
|
false,
|
|
|
|
tokenName,
|
|
|
|
min.core.getParam(min.instance, `${tokenName} Client ID`, null),
|
|
|
|
min.core.getParam(min.instance, `${tokenName} Client Secret`, null),
|
|
|
|
min.core.getParam(min.instance, `${tokenName} Host`, null),
|
|
|
|
min.core.getParam(min.instance, `${tokenName} Tenant`, null)
|
|
|
|
);
|
2023-12-26 11:28:06 -03:00
|
|
|
const expiresOn = await min.adminService.getValue(min.instance.instanceId, `${tokenName}expiresOn`);
|
2023-12-25 17:47:23 -03:00
|
|
|
|
|
|
|
return { token, expiresOn };
|
2023-12-10 12:21:28 -03:00
|
|
|
}
|
|
|
|
|
2019-03-08 06:37:13 -03:00
|
|
|
/**
|
2020-12-28 09:27:35 -03:00
|
|
|
* Calls any REST API by using GET HTTP method.
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* @example user = get "http://server/users/1"
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2019-03-08 06:37:13 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async getByHttp({ pid, url, headers, username, ps, qs }) {
|
|
|
|
let options = {};
|
2023-10-04 15:21:51 -03:00
|
|
|
|
2023-10-05 10:06:03 -03:00
|
|
|
const { min, user, params, proc } = await DialogKeywords.getProcessInfo(pid);
|
2023-10-04 15:21:51 -03:00
|
|
|
GBLogEx.info(min, `GET: ${url}`);
|
|
|
|
|
2023-10-21 14:47:30 -03:00
|
|
|
let pageMode = await DialogKeywords.getOption({ pid, name: 'pageMode' });
|
2024-05-23 23:45:45 -03:00
|
|
|
let continuationToken = await DialogKeywords.getOption({ pid, name: `${proc.executable}-continuationToken` });
|
2023-10-04 15:21:51 -03:00
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
if (pageMode === 'auto' && continuationToken) {
|
2023-10-04 15:21:51 -03:00
|
|
|
headers = headers ? headers : {};
|
|
|
|
|
|
|
|
headers['MS-ContinuationToken'] = continuationToken;
|
|
|
|
}
|
|
|
|
|
2022-08-05 00:10:23 -03:00
|
|
|
if (headers) {
|
|
|
|
options['headers'] = headers;
|
|
|
|
}
|
2023-10-04 15:21:51 -03:00
|
|
|
|
2021-11-22 19:48:53 -03:00
|
|
|
if (username) {
|
|
|
|
options['auth'] = {
|
|
|
|
user: username,
|
|
|
|
pass: ps
|
2022-11-19 23:34:58 -03:00
|
|
|
};
|
2021-11-22 19:48:53 -03:00
|
|
|
}
|
|
|
|
if (qs) {
|
|
|
|
options['qs'] = qs;
|
|
|
|
}
|
2023-11-30 15:47:47 -03:00
|
|
|
let result;
|
|
|
|
await retry(
|
2024-05-23 23:45:45 -03:00
|
|
|
async bail => {
|
2023-11-30 15:47:47 -03:00
|
|
|
result = await fetch(url, options);
|
2021-11-22 19:48:53 -03:00
|
|
|
|
2024-01-08 20:09:02 -03:00
|
|
|
if (result.status === 401) {
|
2024-08-04 17:16:04 -03:00
|
|
|
GBLogEx.info(min, `Waiting 5 secs. before retrying HTTP 401 GET: ${url}`);
|
2024-01-10 15:01:02 -03:00
|
|
|
await GBUtil.sleep(5 * 1000);
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`HTTP:${result.status} retry: ${result.statusText}.`);
|
2024-01-08 20:09:02 -03:00
|
|
|
}
|
|
|
|
if (result.status === 429) {
|
2024-04-21 23:39:39 -03:00
|
|
|
GBLogEx.info(min, `Waiting 1min. before retrying HTTP 429 GET: ${url}`);
|
2024-01-10 15:01:02 -03:00
|
|
|
await GBUtil.sleep(60 * 1000);
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`HTTP:${result.status} retry: ${result.statusText}.`);
|
2023-11-30 15:47:47 -03:00
|
|
|
}
|
2024-01-08 20:09:02 -03:00
|
|
|
if (result.status === 503) {
|
2024-08-04 17:16:04 -03:00
|
|
|
GBLogEx.info(min, `Waiting 1h before retrying GET 503: ${url}`);
|
2024-01-10 15:01:02 -03:00
|
|
|
await GBUtil.sleep(60 * 60 * 1000);
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`HTTP:${result.status} retry: ${result.statusText}.`);
|
2024-01-08 20:09:02 -03:00
|
|
|
}
|
2023-10-04 15:21:51 -03:00
|
|
|
|
2023-11-30 15:47:47 -03:00
|
|
|
if (result.status === 2000) {
|
|
|
|
// Token expired.
|
|
|
|
|
|
|
|
await DialogKeywords.setOption({ pid, name: `${proc.executable}-continuationToken`, value: null });
|
|
|
|
bail(new Error(`Expired Token for ${url}.`));
|
|
|
|
}
|
|
|
|
if (result.status != 200) {
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`GET ${result.status}: ${result.statusText}.`);
|
2023-11-30 15:47:47 -03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
retries: 5,
|
2024-05-23 23:45:45 -03:00
|
|
|
onRetry: err => {
|
|
|
|
GBLog.error(`Retrying HTTP GET due to: ${err.message}.`);
|
|
|
|
}
|
2023-11-30 15:47:47 -03:00
|
|
|
}
|
|
|
|
);
|
2023-10-20 13:39:34 -03:00
|
|
|
let res = JSON.parse(await result.text());
|
2023-10-04 15:21:51 -03:00
|
|
|
|
2023-11-29 00:58:44 -03:00
|
|
|
function process(key, value, o) {
|
|
|
|
if (value === '0000-00-00') {
|
2023-12-07 17:10:57 -03:00
|
|
|
o[key] = null;
|
2023-11-25 10:07:13 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-29 00:58:44 -03:00
|
|
|
function traverse(o, func) {
|
2023-11-25 10:07:13 -03:00
|
|
|
for (var i in o) {
|
2023-11-29 00:58:44 -03:00
|
|
|
func.apply(this, [i, o[i], o]);
|
2024-05-23 23:45:45 -03:00
|
|
|
if (o[i] !== null && typeof o[i] == 'object') {
|
2023-11-29 00:58:44 -03:00
|
|
|
traverse(o[i], func);
|
|
|
|
}
|
2023-11-25 10:07:13 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-29 00:58:44 -03:00
|
|
|
traverse(res, process);
|
2023-11-25 10:07:13 -03:00
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
if (pageMode === 'auto') {
|
2023-10-20 13:39:34 -03:00
|
|
|
continuationToken = res.next?.headers['MS-ContinuationToken'];
|
2023-10-04 15:21:51 -03:00
|
|
|
|
2023-10-20 13:39:34 -03:00
|
|
|
if (continuationToken) {
|
2024-04-21 23:39:39 -03:00
|
|
|
GBLogEx.info(min, `Updating continuationToken for ${url}.`);
|
2023-10-20 13:39:34 -03:00
|
|
|
await DialogKeywords.setOption({ pid, name: 'continuationToken', value: continuationToken });
|
2023-10-04 15:21:51 -03:00
|
|
|
}
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
|
|
|
pageMode = 'none';
|
2023-10-21 14:47:30 -03:00
|
|
|
}
|
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
if (res) {
|
|
|
|
res['pageMode'] = pageMode;
|
|
|
|
}
|
2024-08-23 23:36:20 -03:00
|
|
|
result = null;
|
2023-10-20 13:39:34 -03:00
|
|
|
return res;
|
2021-03-29 18:50:27 -03:00
|
|
|
}
|
2019-03-08 06:37:13 -03:00
|
|
|
|
2022-08-05 00:10:23 -03:00
|
|
|
/**
|
|
|
|
* Calls any REST API by using POST HTTP method.
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
|
|
|
* @example
|
|
|
|
*
|
2022-08-05 19:19:30 -03:00
|
|
|
* user = put "http://server/path", "data"
|
2023-03-04 16:27:25 -03:00
|
|
|
* talk "The updated user area is" + area
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2022-08-05 00:10:23 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async putByHttp({ pid, url, data, headers }) {
|
2024-04-21 23:39:39 -03:00
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2022-08-05 00:10:23 -03:00
|
|
|
const options = {
|
2022-08-05 19:19:30 -03:00
|
|
|
json: data,
|
2024-02-18 23:32:44 -03:00
|
|
|
headers: headers,
|
|
|
|
method: 'PUT'
|
2022-08-05 00:10:23 -03:00
|
|
|
};
|
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
if (typeof data === 'object') {
|
2024-02-20 22:37:16 -03:00
|
|
|
options['body'] = JSON.stringify(data);
|
2024-02-20 22:51:31 -03:00
|
|
|
options.headers['Content-Type'] = 'application/json';
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
2024-02-18 23:32:44 -03:00
|
|
|
options['body'] = data;
|
|
|
|
}
|
|
|
|
|
2022-11-30 09:40:09 -03:00
|
|
|
let result = await fetch(url, options);
|
2024-02-18 23:32:44 -03:00
|
|
|
const text = await result.text();
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `PUT ${url} (${data}): ${text}`);
|
2024-02-18 23:32:44 -03:00
|
|
|
|
2024-02-20 23:00:16 -03:00
|
|
|
if (result.status != 200 && result.status != 201) {
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`PUT ${result.status}: ${result.statusText}.`);
|
2024-02-18 23:32:44 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
let res = JSON.parse(text);
|
|
|
|
return res;
|
2022-08-05 00:10:23 -03:00
|
|
|
}
|
|
|
|
|
2020-08-15 11:39:43 -03:00
|
|
|
/**
|
2020-12-28 09:27:35 -03:00
|
|
|
* Calls any REST API by using POST HTTP method.
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
|
|
|
* @example
|
|
|
|
*
|
2020-12-28 09:27:35 -03:00
|
|
|
* user = post "http://server/path", "data"
|
2023-03-04 16:27:25 -03:00
|
|
|
* talk "The updated user area is" + area
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
2020-08-15 11:39:43 -03:00
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async postByHttp({ pid, url, data, headers }) {
|
2024-04-21 23:39:39 -03:00
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2021-03-29 18:50:27 -03:00
|
|
|
const options = {
|
2023-10-20 13:39:34 -03:00
|
|
|
headers: headers,
|
|
|
|
method: 'POST'
|
2021-03-29 18:50:27 -03:00
|
|
|
};
|
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
if (typeof data === 'object') {
|
2024-02-20 22:37:16 -03:00
|
|
|
options['body'] = JSON.stringify(data);
|
2024-02-20 22:51:31 -03:00
|
|
|
options.headers['Content-Type'] = 'application/json';
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
2023-10-20 13:39:34 -03:00
|
|
|
options['body'] = data;
|
|
|
|
}
|
|
|
|
|
2022-11-30 09:40:09 -03:00
|
|
|
let result = await fetch(url, options);
|
2023-10-20 13:39:34 -03:00
|
|
|
const text = await result.text();
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `POST ${url} (${data}): ${text}`);
|
2022-08-05 19:19:30 -03:00
|
|
|
|
2024-02-20 23:00:16 -03:00
|
|
|
if (result.status != 200 && result.status != 201) {
|
2024-08-23 23:36:20 -03:00
|
|
|
throw new Error(`POST ${result.status}: ${result.statusText}.`);
|
2023-10-20 13:39:34 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
let res = JSON.parse(text);
|
|
|
|
return res;
|
2021-03-29 18:50:27 -03:00
|
|
|
}
|
2019-03-08 06:37:13 -03:00
|
|
|
|
2023-01-26 12:47:37 -03:00
|
|
|
public async numberOnly({ pid, text }) {
|
2021-03-29 18:50:27 -03:00
|
|
|
return text.replace(/\D/gi, '');
|
|
|
|
}
|
2021-08-05 11:20:06 -03:00
|
|
|
|
2022-07-12 13:30:12 -03:00
|
|
|
/**
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
|
|
|
* Fills a .docx or .pptx with template data.
|
|
|
|
*
|
|
|
|
* doc = FILL "templates/template.docx", data
|
|
|
|
*
|
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async fill({ pid, templateName, data }) {
|
2023-02-05 11:57:02 -03:00
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2023-03-04 16:27:25 -03:00
|
|
|
const botId = min.instance.botId;
|
2023-03-09 17:46:34 -03:00
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(botId, 'gbdata');
|
2023-02-06 12:48:41 -03:00
|
|
|
let localName;
|
2022-07-12 13:30:12 -03:00
|
|
|
|
|
|
|
// Downloads template from .gbdrive.
|
|
|
|
|
2023-03-04 16:27:25 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
2023-02-06 12:48:41 -03:00
|
|
|
let path = '/' + urlJoin(gbaiName, `${botId}.gbdrive`);
|
2022-07-12 13:30:12 -03:00
|
|
|
let template = await this.internalGetDocument(client, baseUrl, path, templateName);
|
2023-02-05 11:57:02 -03:00
|
|
|
let url = template['@microsoft.graph.downloadUrl'];
|
2023-02-05 18:19:39 -03:00
|
|
|
const res = await fetch(url);
|
2023-02-06 12:48:41 -03:00
|
|
|
let buf: any = Buffer.from(await res.arrayBuffer());
|
|
|
|
localName = Path.join('work', gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.docx`);
|
2023-02-05 18:19:39 -03:00
|
|
|
Fs.writeFileSync(localName, buf, { encoding: null });
|
2022-07-12 13:30:12 -03:00
|
|
|
|
2023-02-05 18:19:39 -03:00
|
|
|
// Replace image path on all elements of data.
|
2023-02-05 11:57:02 -03:00
|
|
|
|
|
|
|
const images = [];
|
2023-02-06 12:48:41 -03:00
|
|
|
let index = 0;
|
|
|
|
path = Path.join(gbaiName, 'cache', `tmp${GBAdminService.getRndReadableIdentifier()}.docx`);
|
2023-02-13 17:31:38 -03:00
|
|
|
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
|
2023-02-05 11:57:02 -03:00
|
|
|
|
2023-02-06 12:48:41 -03:00
|
|
|
const traverseDataToInjectImageUrl = async o => {
|
|
|
|
for (var i in o) {
|
|
|
|
let value = o[i];
|
2023-02-06 18:14:48 -03:00
|
|
|
|
2023-03-06 07:09:24 -03:00
|
|
|
if (value && value.gbarray) {
|
2023-02-06 18:14:48 -03:00
|
|
|
o.shift();
|
|
|
|
value = o[i];
|
|
|
|
}
|
2023-02-06 12:48:41 -03:00
|
|
|
|
|
|
|
for (const kind of ['png', 'jpg', 'jpeg']) {
|
|
|
|
if (value.endsWith && value.endsWith(`.${kind}`)) {
|
2023-03-04 16:27:25 -03:00
|
|
|
const { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
2023-02-06 12:48:41 -03:00
|
|
|
|
|
|
|
path = urlJoin(gbaiName, `${botId}.gbdrive`);
|
|
|
|
if (value.indexOf('/') !== -1) {
|
|
|
|
path = '/' + urlJoin(path, Path.dirname(value));
|
|
|
|
value = Path.basename(value);
|
|
|
|
}
|
2023-03-06 07:09:24 -03:00
|
|
|
|
2023-02-06 12:48:41 -03:00
|
|
|
const ref = await this.internalGetDocument(client, baseUrl, path, value);
|
|
|
|
let url = ref['@microsoft.graph.downloadUrl'];
|
2023-02-06 18:14:48 -03:00
|
|
|
const imageName = Path.join(
|
|
|
|
'work',
|
|
|
|
gbaiName,
|
|
|
|
'cache',
|
|
|
|
`tmp${GBAdminService.getRndReadableIdentifier()}-${value}.png`
|
|
|
|
);
|
2023-02-06 12:48:41 -03:00
|
|
|
const response = await fetch(url);
|
|
|
|
const buf = Buffer.from(await response.arrayBuffer());
|
|
|
|
Fs.writeFileSync(imageName, buf, { encoding: null });
|
2023-03-06 07:09:24 -03:00
|
|
|
|
2023-02-06 12:48:41 -03:00
|
|
|
const getNormalSize = ({ width, height, orientation }) => {
|
2023-03-06 07:09:24 -03:00
|
|
|
return (orientation || 0) >= 5 ? [height, width] : [width, height];
|
2023-02-06 12:48:41 -03:00
|
|
|
};
|
2023-03-06 07:09:24 -03:00
|
|
|
|
2024-08-23 17:54:47 -03:00
|
|
|
// TODO: sharp. const metadata = await sharp(buf).metadata();
|
2024-05-23 23:45:45 -03:00
|
|
|
const size = getNormalSize({
|
2024-08-23 17:54:47 -03:00
|
|
|
width: 400,
|
|
|
|
height: 400,
|
|
|
|
orientation: '0'
|
2024-05-23 23:45:45 -03:00
|
|
|
});
|
2023-02-06 12:48:41 -03:00
|
|
|
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(imageName));
|
2023-03-06 07:09:24 -03:00
|
|
|
images[index++] = { url: url, size: size, buf: buf };
|
2023-02-06 12:48:41 -03:00
|
|
|
}
|
2023-02-05 11:57:02 -03:00
|
|
|
}
|
|
|
|
if (o[i] !== null && typeof o[i] == 'object') {
|
2023-02-06 12:48:41 -03:00
|
|
|
await traverseDataToInjectImageUrl(o[i]);
|
2023-02-05 11:57:02 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-02-06 18:14:48 -03:00
|
|
|
let indexImage = 0;
|
|
|
|
var opts = {
|
|
|
|
fileType: 'docx',
|
|
|
|
centered: false,
|
|
|
|
getImage: (tagValue, tagName) => {
|
|
|
|
return images[indexImage].buf;
|
|
|
|
},
|
|
|
|
getSize: (img, tagValue, tagName) => {
|
2023-02-08 07:14:02 -03:00
|
|
|
return images[indexImage++].size;
|
2023-02-06 18:14:48 -03:00
|
|
|
}
|
|
|
|
};
|
2023-02-05 18:19:39 -03:00
|
|
|
|
2023-04-09 19:20:15 -03:00
|
|
|
// Loads the file as binary content.
|
|
|
|
|
|
|
|
let zip = new PizZip(buf);
|
2023-02-06 18:14:48 -03:00
|
|
|
let doc = new Docxtemplater();
|
|
|
|
doc.setOptions({ paragraphLoop: true, linebreaks: true });
|
|
|
|
doc.loadZip(zip);
|
|
|
|
if (localName.endsWith('.pptx')) {
|
|
|
|
doc.attachModule(pptxTemplaterModule);
|
2023-02-05 11:57:02 -03:00
|
|
|
}
|
2023-02-06 18:14:48 -03:00
|
|
|
doc.attachModule(new ImageModule(opts));
|
|
|
|
|
|
|
|
await traverseDataToInjectImageUrl(data);
|
2023-03-06 07:09:24 -03:00
|
|
|
doc.setData(data).render();
|
|
|
|
|
2023-02-06 18:14:48 -03:00
|
|
|
buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' });
|
|
|
|
Fs.writeFileSync(localName, buf, { encoding: null });
|
2022-07-12 13:30:12 -03:00
|
|
|
|
2023-02-06 12:48:41 -03:00
|
|
|
return { localName: localName, url: url, data: buf };
|
2022-07-12 13:30:12 -03:00
|
|
|
}
|
2022-08-01 18:36:45 -03:00
|
|
|
|
2023-01-26 12:47:37 -03:00
|
|
|
public screenCapture(pid) {
|
2023-01-10 09:55:30 -03:00
|
|
|
// scrcpy Disabled
|
2022-11-18 22:39:14 -03:00
|
|
|
// function captureImage({ x, y, w, h }) {
|
|
|
|
// const pic = robot.screen.capture(x, y, w, h)
|
|
|
|
// const width = pic.byteWidth / pic.bytesPerPixel // pic.width is sometimes wrong!
|
|
|
|
// const height = pic.height
|
|
|
|
// const image = new Jimp(width, height)
|
|
|
|
// let red, green, blue
|
|
|
|
// pic.image.forEach((byte, i) => {
|
|
|
|
// switch (i % 4) {
|
|
|
|
// case 0: return blue = byte
|
|
|
|
// case 1: return green = byte
|
|
|
|
// case 2: return red = byte
|
|
|
|
// case 3:
|
|
|
|
// image.bitmap.data[i - 3] = red
|
|
|
|
// image.bitmap.data[i - 2] = green
|
|
|
|
// image.bitmap.data[i - 1] = blue
|
|
|
|
// image.bitmap.data[i] = 255
|
|
|
|
// }
|
|
|
|
// })
|
|
|
|
// return image
|
|
|
|
// }
|
|
|
|
// let file = 'out.png';
|
|
|
|
// captureImage({ x: 60, y: 263, w: 250, h: 83 }).write(file)
|
|
|
|
// const config = {
|
|
|
|
// lang: "eng",
|
|
|
|
// oem: 1,
|
|
|
|
// psm: 3,
|
|
|
|
// }
|
|
|
|
// tesseract.recognize(file, config).then(value => {
|
|
|
|
// console.log(value);
|
|
|
|
// });
|
2022-08-01 18:36:45 -03:00
|
|
|
}
|
|
|
|
|
2023-01-26 12:47:37 -03:00
|
|
|
private numberToLetters(num) {
|
2022-11-19 23:34:58 -03:00
|
|
|
let letters = '';
|
2022-08-07 23:54:19 -03:00
|
|
|
while (num >= 0) {
|
2022-11-19 23:34:58 -03:00
|
|
|
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
|
|
|
|
num = Math.floor(num / 26) - 1;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
2022-11-19 23:34:58 -03:00
|
|
|
return letters;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
|
|
|
|
2023-11-30 15:47:47 -03:00
|
|
|
private getTableFromName(file, min) {
|
2023-11-29 00:58:44 -03:00
|
|
|
const minBoot = GBServer.globals.minBoot;
|
|
|
|
const parts = file.split('.');
|
|
|
|
const con = min[parts[0]];
|
|
|
|
if (con) {
|
|
|
|
return con.models[parts[1]];
|
|
|
|
} else {
|
|
|
|
return minBoot.core.sequelize.models[file];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-08 13:56:44 -03:00
|
|
|
private cachedMerge: any = {};
|
2023-11-30 15:47:47 -03:00
|
|
|
|
2022-08-07 23:54:19 -03:00
|
|
|
/**
|
|
|
|
* Merges a multi-value with a tabular file using BY field as key.
|
2022-11-19 23:34:58 -03:00
|
|
|
*
|
|
|
|
* @example
|
|
|
|
*
|
2022-08-07 23:54:19 -03:00
|
|
|
* data = FIND first.xlsx
|
|
|
|
* MERGE "second.xlsx" WITH data BY customer_id
|
|
|
|
*
|
|
|
|
*/
|
2023-01-26 12:47:37 -03:00
|
|
|
public async merge({ pid, file, data, key1, key2 }): Promise<any> {
|
2024-04-21 23:39:39 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
2023-12-17 16:34:03 -03:00
|
|
|
if (!data || data.length === 0) {
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLog.verbose(`MERGE running on ${file}: NO DATA.`);
|
2023-12-17 16:34:03 -03:00
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `MERGE running on ${file} and key1: ${key1}, key2: ${key2}...`);
|
2023-11-30 15:47:47 -03:00
|
|
|
if (!this.cachedMerge[pid]) {
|
2024-05-23 23:45:45 -03:00
|
|
|
this.cachedMerge[pid] = { file: {} };
|
2023-11-30 15:47:47 -03:00
|
|
|
}
|
2023-11-30 13:47:04 -03:00
|
|
|
|
2022-08-07 23:54:19 -03:00
|
|
|
// MAX LINES property.
|
|
|
|
|
|
|
|
let maxLines = 1000;
|
2023-03-04 16:27:25 -03:00
|
|
|
if (user && params && params.maxLines) {
|
|
|
|
if (params.maxLines.toString().toLowerCase() !== 'default') {
|
|
|
|
maxLines = Number.parseInt(params.maxLines).valueOf();
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
|
|
|
}
|
2023-10-20 13:39:34 -03:00
|
|
|
|
2022-08-07 23:54:19 -03:00
|
|
|
// Choose data sources based on file type (HTML Table, data variable or sheet file)
|
2023-10-20 13:39:34 -03:00
|
|
|
|
2023-10-20 17:35:03 -03:00
|
|
|
let storage = file.indexOf('.xlsx') === -1;
|
2022-08-07 23:54:19 -03:00
|
|
|
let results;
|
2024-05-23 23:45:45 -03:00
|
|
|
let header = [],
|
|
|
|
rows = [];
|
2023-10-07 18:40:10 -03:00
|
|
|
let t;
|
2023-11-23 19:55:56 -03:00
|
|
|
let fieldsNames = [];
|
2023-11-24 21:02:32 -03:00
|
|
|
let fieldsSizes = [];
|
2023-12-02 10:21:12 -03:00
|
|
|
let fieldsValuesList = [];
|
2023-10-20 13:39:34 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
if (storage) {
|
2023-11-29 00:58:44 -03:00
|
|
|
t = this.getTableFromName(file, min);
|
|
|
|
|
2023-10-20 17:35:03 -03:00
|
|
|
if (!t) {
|
|
|
|
throw new Error(`TABLE ${file} not found. Check TABLE keywords.`);
|
|
|
|
}
|
2023-11-23 19:55:56 -03:00
|
|
|
|
|
|
|
Object.keys(t.fieldRawAttributesMap).forEach(e => {
|
|
|
|
fieldsNames.push(e);
|
2024-05-23 23:45:45 -03:00
|
|
|
});
|
2023-11-23 19:55:56 -03:00
|
|
|
|
2023-11-24 21:02:32 -03:00
|
|
|
Object.keys(t.fieldRawAttributesMap).forEach(e => {
|
|
|
|
fieldsSizes.push(t.fieldRawAttributesMap[e].size);
|
2024-05-23 23:45:45 -03:00
|
|
|
});
|
2023-11-24 21:02:32 -03:00
|
|
|
|
2023-12-02 10:21:12 -03:00
|
|
|
header = Object.keys(t.fieldRawAttributesMap);
|
2023-12-16 22:28:55 -03:00
|
|
|
|
|
|
|
// In a single execution, several MERGE calls will benift
|
|
|
|
// from caching results across calls.
|
|
|
|
|
2023-11-30 15:47:47 -03:00
|
|
|
if (!this.cachedMerge[pid][file]) {
|
2023-12-02 12:25:43 -03:00
|
|
|
await retry(
|
2024-05-23 23:45:45 -03:00
|
|
|
async bail => {
|
2024-08-25 13:05:26 -03:00
|
|
|
rows = await t.findAll();
|
|
|
|
GBLogEx.info(min, `MERGE cached: ${rows.length} row(s)...`);
|
2023-12-02 12:25:43 -03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
retries: 5,
|
2024-05-23 23:45:45 -03:00
|
|
|
onRetry: err => {
|
|
|
|
GBLog.error(`MERGE: Retrying SELECT ALL on table: ${err.message}.`);
|
|
|
|
}
|
2023-12-02 12:25:43 -03:00
|
|
|
}
|
2023-12-12 23:47:19 -03:00
|
|
|
);
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
2023-12-12 23:47:19 -03:00
|
|
|
rows = this.cachedMerge[pid][file];
|
2023-11-30 15:47:47 -03:00
|
|
|
}
|
2023-10-07 18:40:10 -03:00
|
|
|
} else {
|
|
|
|
const botId = min.instance.botId;
|
|
|
|
const path = DialogKeywords.getGBAIPath(botId, 'gbdata');
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
let document;
|
|
|
|
document = await this.internalGetDocument(client, baseUrl, path, file);
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
// Creates workbook session that will be discarded.
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
let sheets = await client.api(`${baseUrl}/drive/items/${document.id}/workbook/worksheets`).get();
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
results = await client
|
|
|
|
.api(
|
|
|
|
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='A1:CZ${maxLines}')`
|
2023-10-20 13:39:34 -03:00
|
|
|
)
|
2023-10-07 18:40:10 -03:00
|
|
|
.get();
|
2023-10-20 13:39:34 -03:00
|
|
|
|
|
|
|
header = results.text[0];
|
|
|
|
rows = results.text;
|
2024-08-24 00:12:50 -03:00
|
|
|
results = null;
|
2023-10-20 13:39:34 -03:00
|
|
|
}
|
|
|
|
|
2022-08-07 23:54:19 -03:00
|
|
|
let table = [];
|
2023-10-21 12:09:37 -03:00
|
|
|
let foundIndex = 0;
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-11-30 15:47:47 -03:00
|
|
|
// Fills the row variable on the base dataset.
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-11-30 15:47:47 -03:00
|
|
|
if (!storage || !this.cachedMerge[pid][file]) {
|
|
|
|
for (; foundIndex < rows.length; foundIndex++) {
|
|
|
|
let row = {};
|
2024-08-24 00:12:50 -03:00
|
|
|
let tmpRow = rows[foundIndex];
|
2023-11-30 15:47:47 -03:00
|
|
|
row = tmpRow.dataValues ? tmpRow.dataValues : tmpRow;
|
|
|
|
|
|
|
|
for (let colIndex = 0; colIndex < tmpRow.length; colIndex++) {
|
|
|
|
const propertyName = header[colIndex];
|
|
|
|
let value = tmpRow[colIndex];
|
|
|
|
|
2023-12-16 22:28:55 -03:00
|
|
|
if (value && typeof value === 'string' && value.charAt(0) === "'") {
|
2023-11-30 15:47:47 -03:00
|
|
|
if (await this.isValidDate({ pid, dt: value.substr(1) })) {
|
|
|
|
value = value.substr(1);
|
|
|
|
}
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
2023-11-30 15:47:47 -03:00
|
|
|
|
|
|
|
row[propertyName] = value;
|
2024-08-25 13:17:15 -03:00
|
|
|
value = null;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
2023-11-30 15:47:47 -03:00
|
|
|
row['line'] = foundIndex + 1;
|
|
|
|
table.push(row);
|
2024-08-24 00:12:50 -03:00
|
|
|
row = null;
|
|
|
|
tmpRow = null;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
2023-11-30 15:47:47 -03:00
|
|
|
|
|
|
|
if (storage) {
|
|
|
|
this.cachedMerge[pid][file] = table;
|
|
|
|
}
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
2023-11-30 15:47:47 -03:00
|
|
|
table = this.cachedMerge[pid][file];
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2022-08-07 23:54:19 -03:00
|
|
|
let key1Index, key2Index;
|
|
|
|
|
|
|
|
if (key1) {
|
|
|
|
key1Index = _.invertBy(table, key1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (key2) {
|
|
|
|
key2Index = _.invertBy(table, key2);
|
|
|
|
}
|
|
|
|
|
2024-02-22 11:31:25 -03:00
|
|
|
let updates = 0,
|
2024-05-23 23:45:45 -03:00
|
|
|
adds = 0,
|
|
|
|
skipped = 0;
|
2022-08-07 23:54:19 -03:00
|
|
|
|
|
|
|
// Scans all items in incoming data.
|
|
|
|
|
2023-11-24 21:02:32 -03:00
|
|
|
for (let i = 0; i < data.length; i++) {
|
2022-08-07 23:54:19 -03:00
|
|
|
// Scans all sheet lines and compare keys.
|
|
|
|
|
2023-10-21 12:09:37 -03:00
|
|
|
let row = data[i];
|
2023-11-23 19:55:56 -03:00
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
if (GBUtil.hasSubObject(row)) {
|
2024-01-06 22:21:11 -03:00
|
|
|
row = this.flattenJSON(row);
|
2023-11-23 19:55:56 -03:00
|
|
|
}
|
|
|
|
|
2022-08-07 23:54:19 -03:00
|
|
|
let found;
|
2023-10-07 18:40:10 -03:00
|
|
|
let key1Value;
|
2023-11-23 19:55:56 -03:00
|
|
|
let key1Original = key1;
|
2022-08-07 23:54:19 -03:00
|
|
|
if (key1Index) {
|
2023-10-21 12:09:37 -03:00
|
|
|
key1 = key1.charAt(0).toLowerCase() + key1.slice(1);
|
2023-11-23 19:55:56 -03:00
|
|
|
|
2023-11-24 21:02:32 -03:00
|
|
|
Object.keys(row).forEach(e => {
|
2023-11-25 10:07:13 -03:00
|
|
|
if (e.toLowerCase() === key1.toLowerCase()) {
|
2023-11-23 19:55:56 -03:00
|
|
|
key1Value = row[e];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
let foundRow = key1Index[key1Value];
|
2022-10-01 08:44:14 -03:00
|
|
|
if (foundRow) {
|
|
|
|
found = table[foundRow[0]];
|
|
|
|
}
|
2024-08-24 00:12:50 -03:00
|
|
|
foundRow = null;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (found) {
|
2023-11-24 21:02:32 -03:00
|
|
|
let merge = false;
|
2023-10-21 12:09:37 -03:00
|
|
|
for (let j = 0; j < header.length; j++) {
|
2022-08-07 23:54:19 -03:00
|
|
|
const columnName = header[j];
|
2023-11-24 21:02:32 -03:00
|
|
|
let columnNameFound = false;
|
2023-11-23 19:55:56 -03:00
|
|
|
|
|
|
|
let value;
|
2023-11-24 21:02:32 -03:00
|
|
|
Object.keys(row).forEach(e => {
|
2023-11-25 10:07:13 -03:00
|
|
|
if (columnName.toLowerCase() === e.toLowerCase()) {
|
2023-11-23 19:55:56 -03:00
|
|
|
value = row[e];
|
2024-05-23 23:45:45 -03:00
|
|
|
if (typeof value === 'string') {
|
2023-12-17 01:03:04 -03:00
|
|
|
value = value.substring(0, fieldsSizes[j]);
|
|
|
|
}
|
|
|
|
|
2023-11-24 21:02:32 -03:00
|
|
|
columnNameFound = true;
|
2023-11-23 19:55:56 -03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
if (value === undefined) {
|
|
|
|
value = null;
|
|
|
|
}
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-11-23 19:55:56 -03:00
|
|
|
let valueFound;
|
2023-11-24 21:02:32 -03:00
|
|
|
Object.keys(found).forEach(e => {
|
2023-11-25 10:07:13 -03:00
|
|
|
if (columnName.toLowerCase() === e.toLowerCase()) {
|
2023-11-23 19:55:56 -03:00
|
|
|
valueFound = found[e];
|
|
|
|
}
|
|
|
|
});
|
2024-09-02 20:16:56 -03:00
|
|
|
|
2024-01-13 14:23:04 -03:00
|
|
|
const equals =
|
2024-05-23 23:45:45 -03:00
|
|
|
typeof value === 'string' && typeof valueFound === 'string'
|
|
|
|
? value?.toLowerCase() != valueFound?.toLowerCase()
|
|
|
|
: value != valueFound;
|
2024-01-09 16:17:49 -03:00
|
|
|
|
|
|
|
if (equals && columnNameFound) {
|
2023-10-21 12:09:37 -03:00
|
|
|
if (storage) {
|
2023-11-23 19:55:56 -03:00
|
|
|
let obj = {};
|
2023-11-24 21:02:32 -03:00
|
|
|
obj[columnName] = value;
|
|
|
|
let criteria = {};
|
|
|
|
criteria[key1Original] = key1Value;
|
2024-01-13 14:23:04 -03:00
|
|
|
|
2023-11-30 15:47:47 -03:00
|
|
|
await retry(
|
2024-05-23 23:45:45 -03:00
|
|
|
async bail => {
|
2023-11-30 15:47:47 -03:00
|
|
|
await t.update(obj, { where: criteria });
|
2024-05-23 23:45:45 -03:00
|
|
|
},
|
|
|
|
{ retries: 5 }
|
2023-11-30 15:47:47 -03:00
|
|
|
);
|
2024-08-24 00:12:50 -03:00
|
|
|
obj = null;
|
2023-10-21 12:09:37 -03:00
|
|
|
} else {
|
|
|
|
const cell = `${this.numberToLetters(j)}${i + 1}`;
|
|
|
|
const address = `${cell}:${cell}`;
|
2023-10-20 13:39:34 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
await this.set({ pid, handle: null, file, address, value });
|
|
|
|
}
|
2023-11-24 21:02:32 -03:00
|
|
|
merge = true;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
|
|
|
}
|
2023-11-24 21:02:32 -03:00
|
|
|
|
2024-02-22 11:31:25 -03:00
|
|
|
merge ? updates++ : skipped++;
|
2022-11-19 23:34:58 -03:00
|
|
|
} else {
|
2023-10-07 18:40:10 -03:00
|
|
|
let fieldsValues = [];
|
2023-10-21 12:09:37 -03:00
|
|
|
|
2023-11-23 19:55:56 -03:00
|
|
|
for (let j = 0; j < fieldsNames.length; j++) {
|
|
|
|
let add = false;
|
2023-11-24 21:02:32 -03:00
|
|
|
Object.keys(row).forEach(p => {
|
2023-11-25 10:07:13 -03:00
|
|
|
if (fieldsNames[j].toLowerCase() === p.toLowerCase()) {
|
2023-11-24 21:02:32 -03:00
|
|
|
let value = row[p];
|
2024-05-23 23:45:45 -03:00
|
|
|
if (typeof value === 'string') {
|
2023-12-17 01:03:04 -03:00
|
|
|
value = value.substring(0, fieldsSizes[j]);
|
2023-11-24 21:02:32 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
fieldsValues.push(value);
|
2023-11-23 19:55:56 -03:00
|
|
|
add = true;
|
|
|
|
}
|
|
|
|
});
|
2023-11-24 21:02:32 -03:00
|
|
|
if (!add) {
|
2023-11-23 19:55:56 -03:00
|
|
|
fieldsValues.push(null);
|
2023-10-21 12:09:37 -03:00
|
|
|
}
|
2023-11-24 21:02:32 -03:00
|
|
|
}
|
2022-08-07 23:54:19 -03:00
|
|
|
|
2023-10-07 18:40:10 -03:00
|
|
|
if (storage) {
|
2023-12-01 22:02:37 -03:00
|
|
|
// Uppercases fields.
|
2024-02-08 13:56:44 -03:00
|
|
|
|
2024-08-24 00:12:50 -03:00
|
|
|
let dst = {};
|
2023-11-30 16:59:32 -03:00
|
|
|
let i = 0;
|
|
|
|
Object.keys(fieldsValues).forEach(fieldSrc => {
|
2024-01-14 13:58:59 -03:00
|
|
|
const name = fieldsNames[i];
|
|
|
|
const field = name.charAt(0).toUpperCase() + name.slice(1);
|
2023-11-30 16:59:32 -03:00
|
|
|
dst[field] = fieldsValues[fieldSrc];
|
|
|
|
i++;
|
|
|
|
});
|
|
|
|
|
2023-12-02 10:21:12 -03:00
|
|
|
fieldsValuesList.push(dst);
|
2023-11-30 16:59:32 -03:00
|
|
|
this.cachedMerge[pid][file].push(dst);
|
2024-08-24 00:12:50 -03:00
|
|
|
dst = null;
|
2024-05-23 23:45:45 -03:00
|
|
|
} else {
|
2023-10-07 18:40:10 -03:00
|
|
|
await this.save({ pid, file, args: fieldsValues });
|
|
|
|
}
|
2024-08-24 00:12:50 -03:00
|
|
|
fieldsValues = null;
|
2022-08-07 23:54:19 -03:00
|
|
|
adds++;
|
|
|
|
}
|
2024-08-24 00:12:50 -03:00
|
|
|
row = null;
|
2024-08-24 00:22:34 -03:00
|
|
|
found = null;
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
|
|
|
|
2023-12-02 10:21:12 -03:00
|
|
|
// In case of storage, persist to DB in batch.
|
|
|
|
|
2023-12-12 23:47:19 -03:00
|
|
|
if (fieldsValuesList.length) {
|
|
|
|
await this.saveToStorageBatch({ pid, table: file, rows: fieldsValuesList });
|
2023-12-02 10:21:12 -03:00
|
|
|
}
|
2024-08-24 00:12:50 -03:00
|
|
|
key1Index = null;
|
|
|
|
key2Index = null;
|
2023-12-02 10:21:12 -03:00
|
|
|
|
2024-08-23 23:36:20 -03:00
|
|
|
table = null;
|
|
|
|
fieldsValuesList = null;
|
|
|
|
rows = null;
|
|
|
|
header = null;
|
|
|
|
results = null;
|
|
|
|
t = null;
|
|
|
|
|
|
|
|
GBLogEx.info(min, `MERGE results: adds:${adds}, updates:${updates} , skipped: ${skipped}.`);
|
2024-05-23 23:45:45 -03:00
|
|
|
return { title: file, adds, updates, skipped };
|
2022-08-07 23:54:19 -03:00
|
|
|
}
|
2022-09-24 12:51:47 -03:00
|
|
|
|
2023-12-28 10:32:47 -03:00
|
|
|
/**
|
2024-09-02 20:16:56 -03:00
|
|
|
* Publishs a post to BlueSky .
|
2023-12-28 10:32:47 -03:00
|
|
|
*
|
2024-09-02 20:16:56 -03:00
|
|
|
* BlueSky "My BlueSky text"
|
2023-12-28 10:32:47 -03:00
|
|
|
*/
|
2024-09-02 20:16:56 -03:00
|
|
|
public async postToBlueSky({ pid, text }) {
|
2023-02-05 11:57:02 -03:00
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
2023-01-26 12:47:37 -03:00
|
|
|
|
2024-09-02 20:16:56 -03:00
|
|
|
const consumer_key = min.core.getParam(min.instance, 'BlueSky Consumer Key', null);
|
|
|
|
const consumer_secret = min.core.getParam(min.instance, 'BlueSky Consumer Key Secret', null);
|
|
|
|
const access_token_key = min.core.getParam(min.instance, 'BlueSky Access Token', null);
|
|
|
|
const access_token_secret = min.core.getParam(min.instance, 'BlueSky Access Token Secret', null);
|
2022-09-24 12:51:47 -03:00
|
|
|
|
|
|
|
if (!consumer_key || !consumer_secret || !access_token_key || !access_token_secret) {
|
2024-09-02 20:16:56 -03:00
|
|
|
GBLogEx.info(min, 'BlueSky not configured in .gbot.');
|
2022-09-24 12:51:47 -03:00
|
|
|
}
|
2024-09-02 20:16:56 -03:00
|
|
|
throw new Error('Not implemented yet.');
|
2022-09-24 12:51:47 -03:00
|
|
|
|
2024-09-02 20:16:56 -03:00
|
|
|
GBLogEx.info(min, `BlueSky Automation: ${text}.`);
|
2022-09-24 12:51:47 -03:00
|
|
|
}
|
2023-07-23 16:37:21 -03:00
|
|
|
|
2023-08-03 15:15:13 -03:00
|
|
|
/**
|
2023-08-07 19:12:25 -03:00
|
|
|
* HEAR description
|
2023-08-03 15:15:13 -03:00
|
|
|
* text = REWRITE description
|
|
|
|
* SAVE "logs.xlsx", username, text
|
|
|
|
*/
|
|
|
|
public async rewrite({ pid, text }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
const prompt = `rewrite this sentence in a better way: ${text}`;
|
|
|
|
const answer = await ChatServices.continue(min, prompt, 0);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `REWRITE ${text} TO ${answer}`);
|
2023-08-03 15:15:13 -03:00
|
|
|
return answer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-08-07 19:12:25 -03:00
|
|
|
*
|
2023-08-03 15:15:13 -03:00
|
|
|
* qrcode = PAY "10000", "Name", 100
|
|
|
|
* SEND FILE qrcode
|
2023-08-07 19:12:25 -03:00
|
|
|
*
|
2023-08-03 15:15:13 -03:00
|
|
|
*/
|
2023-07-31 15:06:47 -03:00
|
|
|
public async pay({ pid, orderId, customerName, ammount }) {
|
|
|
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
|
|
|
|
const merchantId = min.core.getParam(min.instance, 'Merchant ID', null);
|
|
|
|
const merchantKey = min.core.getParam(min.instance, 'Merchant Key', null);
|
|
|
|
|
|
|
|
if (!merchantId || !merchantKey) {
|
|
|
|
throw new Error('Payment not configured in .gbot.');
|
|
|
|
}
|
|
|
|
|
|
|
|
const apiUrl = 'https://apisandbox.cieloecommerce.cielo.com.br/1/sales/';
|
|
|
|
const requestId = GBAdminService.generateUuid();
|
|
|
|
|
2024-04-21 23:39:39 -03:00
|
|
|
GBLogEx.info(min, `GBPay: ${requestId}, ${orderId}, ${ammount}... `);
|
2023-07-31 15:06:47 -03:00
|
|
|
|
|
|
|
const requestData = {
|
|
|
|
MerchantOrderId: orderId,
|
|
|
|
Customer: {
|
|
|
|
Name: customerName
|
|
|
|
},
|
|
|
|
Payment: {
|
|
|
|
Type: 'qrcode',
|
|
|
|
Amount: ammount,
|
|
|
|
Installments: 1,
|
|
|
|
Capture: false,
|
|
|
|
Modality: 'Debit'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const response = await fetch(apiUrl, {
|
|
|
|
method: 'POST',
|
|
|
|
body: JSON.stringify(requestData),
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
MerchantId: merchantId,
|
|
|
|
MerchantKey: merchantKey,
|
|
|
|
RequestId: requestId
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
|
|
}
|
|
|
|
const data = await response.json();
|
2023-08-07 19:12:25 -03:00
|
|
|
|
2023-07-31 15:06:47 -03:00
|
|
|
// Prepare an image on cache and return the GBFILE information.
|
2023-08-07 19:12:25 -03:00
|
|
|
|
2023-07-31 15:06:47 -03:00
|
|
|
const buf = Buffer.from(data.Payment.QrCodeBase64Image, 'base64');
|
|
|
|
const localName = Path.join('work', gbaiName, 'cache', `qr${GBAdminService.getRndReadableIdentifier()}.png`);
|
|
|
|
Fs.writeFileSync(localName, buf, { encoding: null });
|
|
|
|
const url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName));
|
2023-08-07 19:12:25 -03:00
|
|
|
|
2024-04-21 23:39:39 -03:00
|
|
|
GBLogEx.info(min, `GBPay: ${data.MerchantOrderId} OK: ${url}.`);
|
2023-07-31 15:06:47 -03:00
|
|
|
|
2023-08-07 19:12:25 -03:00
|
|
|
return {
|
|
|
|
name: Path.basename(localName),
|
|
|
|
localName: localName,
|
|
|
|
url: url,
|
|
|
|
data: buf,
|
|
|
|
text: data.Payment.QrCodeString
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* HEAR logo AS FILE
|
|
|
|
* file = AUTO SAVE logo
|
|
|
|
* TALK "Your " + file.name + " file is saved."
|
|
|
|
*/
|
|
|
|
public async autoSave({ pid, handle }) {
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
this.internalAutoSave({ min, handle });
|
|
|
|
}
|
|
|
|
|
2023-08-21 12:42:59 -03:00
|
|
|
private async internalAutoSave({ min, handle }) {
|
2023-08-07 19:12:25 -03:00
|
|
|
const file = GBServer.globals.files[handle];
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `Auto saving '${file.filename}' (SAVE file).`);
|
2023-08-07 19:12:25 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
2023-08-08 09:05:38 -03:00
|
|
|
|
2023-08-07 19:12:25 -03:00
|
|
|
const path = DialogKeywords.getGBAIPath(min.botId, `gbdrive`);
|
2023-08-21 12:42:59 -03:00
|
|
|
const fileName = file.url ? file.url : file.name;
|
2023-08-08 09:05:38 -03:00
|
|
|
const contentType = mime.lookup(fileName);
|
|
|
|
const ext = Path.extname(fileName).substring(1);
|
|
|
|
const kind = await this.getExtensionInfo(ext);
|
|
|
|
|
2023-08-20 16:09:29 -03:00
|
|
|
let d = new Date(),
|
|
|
|
month = '' + (d.getMonth() + 1),
|
|
|
|
day = '' + d.getDate(),
|
|
|
|
year = d.getFullYear();
|
|
|
|
|
2023-08-21 12:42:59 -03:00
|
|
|
const today = [day, month, year].join('-');
|
|
|
|
const result = await client
|
|
|
|
.api(`${baseUrl}/drive/root:/${path}/${today}/${kind.category}/${fileName}:/content`)
|
|
|
|
.put(file.data);
|
2023-08-08 09:05:38 -03:00
|
|
|
|
2023-08-21 12:42:59 -03:00
|
|
|
return { contentType, ext, kind, category: kind['category'] };
|
2023-08-07 19:12:25 -03:00
|
|
|
}
|
2023-08-21 12:42:59 -03:00
|
|
|
|
2024-01-13 14:23:04 -03:00
|
|
|
public async deleteFromStorage({ pid, table, criteria }) {
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `DELETE '${table}' where ${criteria}.`);
|
2024-01-13 14:23:04 -03:00
|
|
|
|
|
|
|
const definition = this.getTableFromName(table, min);
|
|
|
|
const filter = await SystemKeywords.getFilter(criteria);
|
|
|
|
|
|
|
|
await retry(
|
2024-05-23 23:45:45 -03:00
|
|
|
async bail => {
|
2024-01-13 14:23:04 -03:00
|
|
|
const options = { where: {} };
|
|
|
|
options.where = {};
|
|
|
|
|
|
|
|
options.where[filter['columnName']] = filter['value'];
|
|
|
|
await definition.destroy(options);
|
|
|
|
},
|
|
|
|
{
|
|
|
|
retries: 5,
|
2024-05-23 23:45:45 -03:00
|
|
|
onRetry: err => {
|
2024-08-24 00:12:50 -03:00
|
|
|
GBLog.error(`Retrying deleteFromStorage due to: ${err.message}.`);
|
2024-05-23 23:45:45 -03:00
|
|
|
}
|
2024-01-13 14:23:04 -03:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
public async deleteFile({ pid, file }) {
|
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-23 23:36:20 -03:00
|
|
|
GBLogEx.info(min, `DELETE '${file.name}'.`);
|
2023-12-13 15:33:00 -03:00
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
const gbaiPath = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
const fileName = file.name;
|
2023-12-13 15:33:00 -03:00
|
|
|
const contentType = mime.lookup(fileName);
|
|
|
|
const ext = Path.extname(fileName).substring(1);
|
|
|
|
const kind = await this.getExtensionInfo(ext);
|
|
|
|
|
2024-05-23 23:45:45 -03:00
|
|
|
await client.api(`${baseUrl}/drive/root:/${gbaiPath}/${file.path}`).delete();
|
2023-12-13 15:33:00 -03:00
|
|
|
|
|
|
|
return { contentType, ext, kind, category: kind['category'] };
|
|
|
|
}
|
|
|
|
|
2023-08-08 09:05:38 -03:00
|
|
|
public async getExtensionInfo(ext: any): Promise<any> {
|
|
|
|
let array = exts.filter((v, i, a) => a[i]['extension'] === ext);
|
2023-08-21 12:42:59 -03:00
|
|
|
if (array[0]) {
|
2023-08-08 09:05:38 -03:00
|
|
|
return array[0];
|
|
|
|
}
|
2023-08-21 12:42:59 -03:00
|
|
|
return { category: 'Other', description: 'General documents' };
|
2023-07-31 15:06:47 -03:00
|
|
|
}
|
2023-12-12 19:53:05 -03:00
|
|
|
|
2023-12-12 23:47:19 -03:00
|
|
|
/**
|
2024-05-23 23:45:45 -03:00
|
|
|
* Loads all para from tabular file Config.xlsx.
|
|
|
|
*/
|
2023-12-19 07:16:20 -03:00
|
|
|
public async dirFolder({ pid, remotePath, baseUrl = null, client = null, array = null }) {
|
2023-12-14 12:34:41 -03:00
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2023-12-12 23:47:19 -03:00
|
|
|
GBLogEx.info(min, `dirFolder: remotePath=${remotePath}, baseUrl=${baseUrl}`);
|
|
|
|
|
2024-04-13 12:24:08 -03:00
|
|
|
// In case of empty files, build an zero element array.
|
|
|
|
|
2023-12-18 11:14:38 -03:00
|
|
|
if (!array) {
|
|
|
|
array = [];
|
|
|
|
}
|
|
|
|
|
2023-12-25 17:47:23 -03:00
|
|
|
if (!baseUrl) {
|
2023-12-19 07:16:20 -03:00
|
|
|
let obj = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
baseUrl = obj.baseUrl;
|
|
|
|
client = obj.client;
|
|
|
|
}
|
2023-12-18 11:14:38 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
remotePath = remotePath.replace(/\\/gi, '/');
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
// Retrieves all files in remote folder.
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
let path = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
path = urlJoin(path, remotePath);
|
|
|
|
let url = `${baseUrl}/drive/root:/${path}:/children`;
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
const res = await client.api(url).get();
|
|
|
|
const documents = res.value;
|
|
|
|
if (documents === undefined || documents.length === 0) {
|
|
|
|
GBLogEx.info(min, `${remotePath} is an empty folder.`);
|
|
|
|
return array;
|
|
|
|
}
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
// Navigate files / directory to recurse.
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
await CollectionUtil.asyncForEach(documents, async item => {
|
|
|
|
if (item.folder) {
|
|
|
|
remotePath = urlJoin(remotePath, item.name);
|
2024-05-23 23:45:45 -03:00
|
|
|
array = [...array, ...(await this.dirFolder({ pid, remotePath, baseUrl, client, array }))];
|
2023-12-19 07:16:20 -03:00
|
|
|
} else {
|
|
|
|
// TODO: https://raw.githubusercontent.com/ishanarora04/quickxorhash/master/quickxorhash.js
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
let obj = {};
|
|
|
|
obj['modified'] = item.lastModifiedDateTime;
|
|
|
|
obj['name'] = item.name;
|
|
|
|
obj['size'] = item.size;
|
|
|
|
obj['hash'] = item.file?.hashes?.quickXorHash;
|
|
|
|
obj['path'] = Path.join(remotePath, item.name);
|
|
|
|
obj['url'] = item['@microsoft.graph.downloadUrl'];
|
2023-12-12 23:47:19 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
array.push(obj);
|
|
|
|
}
|
|
|
|
});
|
2023-12-18 11:14:38 -03:00
|
|
|
|
2023-12-19 07:16:20 -03:00
|
|
|
return array;
|
2023-12-12 23:47:19 -03:00
|
|
|
}
|
2024-01-06 22:21:11 -03:00
|
|
|
|
2024-08-24 11:35:22 -03:00
|
|
|
public async log({ pid, obj }) {
|
2024-01-06 22:21:11 -03:00
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-08-24 11:35:22 -03:00
|
|
|
GBLogEx.info(min, GBUtil.toYAML(obj));
|
2024-01-06 22:21:11 -03:00
|
|
|
}
|
2024-06-27 18:45:33 -03:00
|
|
|
|
2024-06-27 19:19:43 -03:00
|
|
|
public async getPdf({ pid, file }) {
|
2024-06-27 18:45:33 -03:00
|
|
|
const { min } = await DialogKeywords.getProcessInfo(pid);
|
2024-06-27 19:19:43 -03:00
|
|
|
GBLogEx.info(min, `BASIC GET (pdf): ${file}`);
|
2024-06-27 18:45:33 -03:00
|
|
|
|
2024-09-04 15:23:56 -03:00
|
|
|
let data ;
|
|
|
|
|
|
|
|
if (GBConfigService.get('STORAGE_NAME')) {
|
|
|
|
let { baseUrl, client } = await GBDeployer.internalGetDriveClient(min);
|
|
|
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
|
|
|
let path = '/' + urlJoin(gbaiName, `${min.botId}.gbdrive`);
|
|
|
|
let template = await this.internalGetDocument(client, baseUrl, path, file);
|
|
|
|
let url = template['@microsoft.graph.downloadUrl'];
|
|
|
|
const res = await fetch(url);
|
|
|
|
let buf: any = Buffer.from(await res.arrayBuffer());
|
|
|
|
data = new Uint8Array(buf);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
let path = DialogKeywords.getGBAIPath(min.botId, `gbdrive`);
|
|
|
|
let filePath = Path.join(GBConfigService.get('STORAGE_LIBRARY'), path, file);
|
|
|
|
data = Fs.readFileSync(filePath, 'utf8');
|
|
|
|
data = new Uint8Array(Buffer.from(data, 'utf8'));
|
|
|
|
}
|
|
|
|
|
2024-06-27 18:45:33 -03:00
|
|
|
const pdf = await getDocument({ data }).promise;
|
2024-08-04 17:16:04 -03:00
|
|
|
let pages = [];
|
2024-06-27 18:45:33 -03:00
|
|
|
|
|
|
|
for (let i = 1; i <= pdf.numPages; i++) {
|
|
|
|
const page = await pdf.getPage(i);
|
|
|
|
const textContent = await page.getTextContent();
|
|
|
|
const text = textContent.items
|
|
|
|
.map(item => item['str'])
|
|
|
|
.join('')
|
|
|
|
.replace(/\s/g, '');
|
2024-08-04 17:16:04 -03:00
|
|
|
pages.push(text);
|
2024-06-27 18:45:33 -03:00
|
|
|
}
|
|
|
|
|
2024-08-04 17:16:04 -03:00
|
|
|
return pages.join('');
|
2024-06-27 18:45:33 -03:00
|
|
|
}
|
|
|
|
|
2024-08-04 17:16:04 -03:00
|
|
|
public async setContext({ pid, text }) {
|
2024-06-27 18:45:33 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
ChatServices.userSystemPrompt[user.userSystemId] = text;
|
2024-08-16 10:43:15 -03:00
|
|
|
|
2024-08-04 17:16:04 -03:00
|
|
|
await this.setMemoryContext({ pid, erase: true });
|
2024-06-27 18:45:33 -03:00
|
|
|
}
|
|
|
|
|
2024-08-04 17:16:04 -03:00
|
|
|
public async setMemoryContext({ pid, input = null, output = null, erase }) {
|
2024-06-29 09:09:50 -03:00
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
let memory;
|
|
|
|
if (erase || !ChatServices.memoryMap[user.userSystemId]) {
|
|
|
|
memory = new BufferWindowMemory({
|
|
|
|
returnMessages: true,
|
|
|
|
memoryKey: 'chat_history',
|
|
|
|
inputKey: 'input',
|
|
|
|
k: 2
|
|
|
|
});
|
|
|
|
|
|
|
|
ChatServices.memoryMap[user.userSystemId] = memory;
|
|
|
|
} else {
|
|
|
|
memory = ChatServices.memoryMap[user.userSystemId];
|
|
|
|
}
|
|
|
|
|
2024-08-04 17:16:04 -03:00
|
|
|
if (memory && input)
|
|
|
|
await memory.saveContext(
|
|
|
|
{
|
|
|
|
input: input
|
|
|
|
},
|
|
|
|
{
|
|
|
|
output: output
|
|
|
|
}
|
|
|
|
);
|
2024-06-29 09:09:50 -03:00
|
|
|
}
|
2024-08-16 10:43:15 -03:00
|
|
|
|
2024-09-04 14:58:11 -03:00
|
|
|
public async postToFacebook({ pid, imagePath, caption, pageId }) {
|
|
|
|
// Obtendo informações do processo para logs (ajuste conforme necessário)
|
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
|
|
|
|
// Leitura do arquivo de imagem
|
|
|
|
const imageBuffer = Fs.readFileSync(Path.resolve(imagePath));
|
|
|
|
|
|
|
|
// Criação de um arquivo temporário para enviar
|
|
|
|
const tempFilePath = Path.resolve('temp_image.jpg');
|
|
|
|
Fs.writeFileSync(tempFilePath, imageBuffer);
|
|
|
|
|
|
|
|
// Publicação da imagem
|
|
|
|
const page = new Page(pageId);
|
|
|
|
const response = await page.createFeed({
|
|
|
|
message: caption,
|
|
|
|
attached_media: [
|
|
|
|
{
|
|
|
|
media_fbid: tempFilePath,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
// Log do resultado
|
|
|
|
GBLogEx.info(min, `Imagem publicada no Facebook: ${JSON.stringify(response)}`);
|
|
|
|
|
|
|
|
// Limpeza do arquivo temporário
|
|
|
|
Fs.unlinkSync(tempFilePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-08-16 10:43:15 -03:00
|
|
|
public async postToInstagram({ pid, username, password, imagePath, caption }) {
|
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
|
|
|
|
const ig = new IgApiClient();
|
|
|
|
ig.state.generateDevice(username);
|
|
|
|
await ig.account.login(username, password);
|
|
|
|
const imageBuffer = readFileSync(resolve(imagePath));
|
|
|
|
const publishResult = await ig.publish.photo({
|
|
|
|
file: imageBuffer,
|
|
|
|
caption
|
|
|
|
});
|
|
|
|
|
|
|
|
GBLogEx.info(min, `Image posted on IG: ${publishResult}`);
|
|
|
|
}
|
2024-09-04 16:48:08 -03:00
|
|
|
|
|
|
|
public async setAnswerMode({ pid, mode }) {
|
|
|
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
|
|
|
|
|
|
|
ChatServices.usersMode[user.userSystemId] = mode;
|
|
|
|
|
|
|
|
GBLogEx.info(min, `LLM Mode (user.userSystemId) : ${mode}`);
|
|
|
|
}
|
|
|
|
|
2023-07-31 15:06:47 -03:00
|
|
|
}
|