new(whatsapp.gblib): Auto-create WhatsApp templates from articles in .docx.
This commit is contained in:
parent
1bb297f68b
commit
e8d0317f82
9 changed files with 383 additions and 209 deletions
|
@ -64,8 +64,6 @@ import { GBUtil } from '../../../src/util.js';
|
||||||
import SwaggerClient from 'swagger-client';
|
import SwaggerClient from 'swagger-client';
|
||||||
import { GBVMService } from './GBVMService.js';
|
import { GBVMService } from './GBVMService.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default check interval for user replay
|
* Default check interval for user replay
|
||||||
*/
|
*/
|
||||||
|
@ -212,28 +210,28 @@ export class DialogKeywords {
|
||||||
*
|
*
|
||||||
* @example EXIT
|
* @example EXIT
|
||||||
*/
|
*/
|
||||||
public async exit({ }) { }
|
public async exit({}) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get active tasks.
|
* Get active tasks.
|
||||||
*
|
*
|
||||||
* @example list = ACTIVE TASKS
|
* @example list = ACTIVE TASKS
|
||||||
*/
|
*/
|
||||||
public async getActiveTasks({ pid }) { }
|
public async getActiveTasks({ pid }) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new deal.
|
* Creates a new deal.
|
||||||
*
|
*
|
||||||
* @example CREATE DEAL dealname,contato,empresa,amount
|
* @example CREATE DEAL dealname,contato,empresa,amount
|
||||||
*/
|
*/
|
||||||
public async createDeal({ pid, dealName, contact, company, amount }) { }
|
public async createDeal({ pid, dealName, contact, company, amount }) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds contacts in XRM.
|
* Finds contacts in XRM.
|
||||||
*
|
*
|
||||||
* @example list = FIND CONTACT "Sandra"
|
* @example list = FIND CONTACT "Sandra"
|
||||||
*/
|
*/
|
||||||
public async fndContact({ pid, name }) { }
|
public async fndContact({ pid, name }) {}
|
||||||
|
|
||||||
public getContentLocaleWithCulture(contentLocale) {
|
public getContentLocaleWithCulture(contentLocale) {
|
||||||
switch (contentLocale) {
|
switch (contentLocale) {
|
||||||
|
@ -324,7 +322,6 @@ export class DialogKeywords {
|
||||||
|
|
||||||
// https://weblog.west-wind.com/posts/2008/Mar/18/A-simple-formatDate-function-for-JavaScript
|
// https://weblog.west-wind.com/posts/2008/Mar/18/A-simple-formatDate-function-for-JavaScript
|
||||||
public async format({ pid, value, format }) {
|
public async format({ pid, value, format }) {
|
||||||
|
|
||||||
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||||
const contentLocale = min.core.getParam(
|
const contentLocale = min.core.getParam(
|
||||||
min.instance,
|
min.instance,
|
||||||
|
@ -337,39 +334,31 @@ export class DialogKeywords {
|
||||||
}
|
}
|
||||||
var date: any = new Date(value); //don't change original date
|
var date: any = new Date(value); //don't change original date
|
||||||
|
|
||||||
if (!format)
|
if (!format) format = 'MM/dd/yyyy';
|
||||||
format = "MM/dd/yyyy";
|
|
||||||
|
|
||||||
var month = date.getMonth() + 1;
|
var month = date.getMonth() + 1;
|
||||||
var year = date.getFullYear();
|
var year = date.getFullYear();
|
||||||
|
|
||||||
format = format.replace("MM", GBUtil.padL(month.toString(), 2, "0"));
|
format = format.replace('MM', GBUtil.padL(month.toString(), 2, '0'));
|
||||||
|
|
||||||
if (format.indexOf("yyyy") > -1)
|
if (format.indexOf('yyyy') > -1) format = format.replace('yyyy', year.toString());
|
||||||
format = format.replace("yyyy", year.toString());
|
else if (format.indexOf('yy') > -1) format = format.replace('yy', year.toString().substr(2, 2));
|
||||||
else if (format.indexOf("yy") > -1)
|
|
||||||
format = format.replace("yy", year.toString().substr(2, 2));
|
|
||||||
|
|
||||||
format = format.replace("dd", GBUtil.padL(date.getDate().toString(), 2, "0"));
|
format = format.replace('dd', GBUtil.padL(date.getDate().toString(), 2, '0'));
|
||||||
|
|
||||||
var hours = date.getHours();
|
var hours = date.getHours();
|
||||||
if (format.indexOf("t") > -1) {
|
if (format.indexOf('t') > -1) {
|
||||||
if (hours > 11)
|
if (hours > 11) format = format.replace('t', 'pm');
|
||||||
format = format.replace("t", "pm")
|
else format = format.replace('t', 'am');
|
||||||
else
|
|
||||||
format = format.replace("t", "am")
|
|
||||||
}
|
}
|
||||||
if (format.indexOf("HH") > -1)
|
if (format.indexOf('HH') > -1) format = format.replace('HH', GBUtil.padL(hours.toString(), 2, '0'));
|
||||||
format = format.replace("HH", GBUtil.padL(hours.toString(), 2, "0"));
|
if (format.indexOf('hh') > -1) {
|
||||||
if (format.indexOf("hh") > -1) {
|
|
||||||
if (hours > 12) hours - 12;
|
if (hours > 12) hours - 12;
|
||||||
if (hours == 0) hours = 12;
|
if (hours == 0) hours = 12;
|
||||||
format = format.replace("hh", hours.toString().padL(2, "0"));
|
format = format.replace('hh', hours.toString().padL(2, '0'));
|
||||||
}
|
}
|
||||||
if (format.indexOf("mm") > -1)
|
if (format.indexOf('mm') > -1) format = format.replace('mm', GBUtil.padL(date.getMinutes().toString(), 2, '0'));
|
||||||
format = format.replace("mm", GBUtil.padL(date.getMinutes().toString(), 2, "0"));
|
if (format.indexOf('ss') > -1) format = format.replace('ss', GBUtil.padL(date.getSeconds().toString(), 2, '0'));
|
||||||
if (format.indexOf("ss") > -1)
|
|
||||||
format = format.replace("ss", GBUtil.padL(date.getSeconds().toString(), 2, "0"));
|
|
||||||
|
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
@ -382,7 +371,6 @@ export class DialogKeywords {
|
||||||
* https://stackoverflow.com/a/1214753/18511
|
* https://stackoverflow.com/a/1214753/18511
|
||||||
*/
|
*/
|
||||||
public async dateAdd({ pid, date, mode, units }) {
|
public async dateAdd({ pid, date, mode, units }) {
|
||||||
|
|
||||||
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||||
const contentLocale = min.core.getParam(
|
const contentLocale = min.core.getParam(
|
||||||
min.instance,
|
min.instance,
|
||||||
|
@ -530,13 +518,13 @@ export class DialogKeywords {
|
||||||
public async sendEmail({ pid, to, subject, body }) {
|
public async sendEmail({ pid, to, subject, body }) {
|
||||||
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
const { min, user } = await DialogKeywords.getProcessInfo(pid);
|
||||||
|
|
||||||
if (!process.env.EMAIL_FROM){
|
if (!process.env.EMAIL_FROM) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!body ) {
|
if (!body) {
|
||||||
body = "";
|
body = '';
|
||||||
};
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line:no-console
|
// tslint:disable-next-line:no-console
|
||||||
|
|
||||||
|
@ -550,7 +538,6 @@ export class DialogKeywords {
|
||||||
body = result.value;
|
body = result.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (emailToken) {
|
if (emailToken) {
|
||||||
return new Promise<any>((resolve, reject) => {
|
return new Promise<any>((resolve, reject) => {
|
||||||
sgMail.setApiKey(emailToken);
|
sgMail.setApiKey(emailToken);
|
||||||
|
@ -569,39 +556,35 @@ export class DialogKeywords {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
let { client } = await GBDeployer.internalGetDriveClient(min);
|
let { client } = await GBDeployer.internalGetDriveClient(min);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
"message": {
|
message: {
|
||||||
"subject": subject,
|
subject: subject,
|
||||||
"body": {
|
body: {
|
||||||
"contentType": "Text",
|
contentType: 'Text',
|
||||||
"content": body
|
content: body
|
||||||
},
|
},
|
||||||
"toRecipients": [
|
toRecipients: [
|
||||||
{
|
{
|
||||||
"emailAddress": {
|
emailAddress: {
|
||||||
"address": to
|
address: to
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"from": {
|
from: {
|
||||||
"emailAddress": {
|
emailAddress: {
|
||||||
"address": process.env.EMAIL_FROM
|
address: process.env.EMAIL_FROM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await client.api('/me/sendMail')
|
await client.api('/me/sendMail').post(data);
|
||||||
.post(data);
|
|
||||||
|
|
||||||
GBLogEx.info(min, `E-mail ${to} (${subject}) sent.`);
|
GBLogEx.info(min, `E-mail ${to} (${subject}) sent.`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -622,7 +605,7 @@ export class DialogKeywords {
|
||||||
* @example SEND TEMPLATE TO "+199988887777","image.jpg"
|
* @example SEND TEMPLATE TO "+199988887777","image.jpg"
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public async sendTemplateTo({ pid, mobile, filename}) {
|
public async sendTemplateTo({ pid, mobile, filename }) {
|
||||||
const { min, user, proc } = await DialogKeywords.getProcessInfo(pid);
|
const { min, user, proc } = await DialogKeywords.getProcessInfo(pid);
|
||||||
GBLogEx.info(min, `BASIC: SEND TEMPLATE TO '${mobile}',filename '${filename}'.`);
|
GBLogEx.info(min, `BASIC: SEND TEMPLATE TO '${mobile}',filename '${filename}'.`);
|
||||||
const service = new GBConversationalService(min.core);
|
const service = new GBConversationalService(min.core);
|
||||||
|
@ -630,14 +613,11 @@ export class DialogKeywords {
|
||||||
let text;
|
let text;
|
||||||
if (filename.endsWith('.docx')) {
|
if (filename.endsWith('.docx')) {
|
||||||
text = await min.kbService.getAnswerTextByMediaName(min.instance.instanceId, filename);
|
text = await min.kbService.getAnswerTextByMediaName(min.instance.instanceId, filename);
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
text = filename;
|
text = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return await service.fillAndBroadcastTemplate(min, mobile, text);
|
return await service.fillAndBroadcastTemplate(min, filename, mobile, text);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -686,20 +666,17 @@ export class DialogKeywords {
|
||||||
|
|
||||||
// Checks access.
|
// Checks access.
|
||||||
|
|
||||||
const filters = ["People.xlsx", `${role}=x`, `id=${user.userSystemId}`];
|
const filters = ['People.xlsx', `${role}=x`, `id=${user.userSystemId}`];
|
||||||
const people = await sys.find({ pid, handle: null, args: filters });
|
const people = await sys.find({ pid, handle: null, args: filters });
|
||||||
|
|
||||||
if (!people) {
|
if (!people) {
|
||||||
throw new Error(`Invalid access. Check if People sheet has the role ${role} checked.`);
|
throw new Error(`Invalid access. Check if People sheet has the role ${role} checked.`);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
GBLogEx.info(min, `Allowed access for ${user.userSystemId} on ${role}`);
|
GBLogEx.info(min, `Allowed access for ${user.userSystemId} on ${role}`);
|
||||||
return people;
|
return people;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the id generation policy.
|
* Defines the id generation policy.
|
||||||
*
|
*
|
||||||
|
@ -787,7 +764,6 @@ export class DialogKeywords {
|
||||||
await DialogKeywords.setOption({ pid, name, value });
|
await DialogKeywords.setOption({ pid, name, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns current if any continuation token for paginated HTTP requests.
|
* Returns current if any continuation token for paginated HTTP requests.
|
||||||
*
|
*
|
||||||
|
@ -836,7 +812,7 @@ export class DialogKeywords {
|
||||||
* Defines page mode for paged GET calls.
|
* Defines page mode for paged GET calls.
|
||||||
*
|
*
|
||||||
* @example SET PAGE MODE "auto"
|
* @example SET PAGE MODE "auto"
|
||||||
* data = GET url
|
* data = GET url
|
||||||
* FOR EACH item in data
|
* FOR EACH item in data
|
||||||
* ...
|
* ...
|
||||||
* END FOR
|
* END FOR
|
||||||
|
@ -916,7 +892,7 @@ export class DialogKeywords {
|
||||||
* @example MENU
|
* @example MENU
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public async showMenu({ }) {
|
public async showMenu({}) {
|
||||||
// https://github.com/GeneralBots/BotServer/issues/237
|
// https://github.com/GeneralBots/BotServer/issues/237
|
||||||
// return await beginDialog('/menu');
|
// return await beginDialog('/menu');
|
||||||
}
|
}
|
||||||
|
@ -943,7 +919,6 @@ export class DialogKeywords {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public async hear({ pid, kind, args }) {
|
public async hear({ pid, kind, args }) {
|
||||||
|
|
||||||
let { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
let { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
||||||
|
|
||||||
// Handles first arg as an array of args.
|
// Handles first arg as an array of args.
|
||||||
|
@ -1122,7 +1097,6 @@ export class DialogKeywords {
|
||||||
|
|
||||||
result = answer;
|
result = answer;
|
||||||
} else if (kind === 'date') {
|
} else if (kind === 'date') {
|
||||||
|
|
||||||
const parseDate = str => {
|
const parseDate = str => {
|
||||||
function pad(x) {
|
function pad(x) {
|
||||||
return (('' + x).length == 2 ? '' : '0') + x;
|
return (('' + x).length == 2 ? '' : '0') + x;
|
||||||
|
@ -1298,15 +1272,7 @@ export class DialogKeywords {
|
||||||
let sec = new SecService();
|
let sec = new SecService();
|
||||||
let user = await sec.getUserFromSystemId(fromOrDialogName);
|
let user = await sec.getUserFromSystemId(fromOrDialogName);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await sec.ensureUser(
|
user = await sec.ensureUser(min, fromOrDialogName, fromOrDialogName, null, 'whatsapp', 'from', null);
|
||||||
min,
|
|
||||||
fromOrDialogName,
|
|
||||||
fromOrDialogName,
|
|
||||||
null,
|
|
||||||
'whatsapp',
|
|
||||||
'from',
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await sec.updateUserHearOnDialog(user.userId, dialogName);
|
await sec.updateUserHearOnDialog(user.userId, dialogName);
|
||||||
}
|
}
|
||||||
|
@ -1341,7 +1307,6 @@ export class DialogKeywords {
|
||||||
|
|
||||||
let count = API_RETRIES;
|
let count = API_RETRIES;
|
||||||
while (count--) {
|
while (count--) {
|
||||||
|
|
||||||
await GBUtil.sleep(DEFAULT_HEAR_POLL_INTERVAL);
|
await GBUtil.sleep(DEFAULT_HEAR_POLL_INTERVAL);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1364,33 +1329,23 @@ export class DialogKeywords {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
GBLog.error(
|
GBLog.error(
|
||||||
`Error calling printMessages in messageBot API ${err.data === undefined ? err : err.data} ${err.errObj ? err.errObj.message : ''
|
`Error calling printMessages in messageBot API ${err.data === undefined ? err : err.data} ${
|
||||||
|
err.errObj ? err.errObj.message : ''
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async start({ botId, botApiKey, userSystemId, text }) {
|
public async start({ botId, botApiKey, userSystemId, text }) {
|
||||||
|
|
||||||
let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0];
|
let min: GBMinInstance = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0];
|
||||||
let sec = new SecService();
|
let sec = new SecService();
|
||||||
let user = await sec.getUserFromSystemId(userSystemId);
|
let user = await sec.getUserFromSystemId(userSystemId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await sec.ensureUser(
|
user = await sec.ensureUser(min, userSystemId, userSystemId, null, 'api', 'API User', null);
|
||||||
min,
|
|
||||||
userSystemId,
|
|
||||||
userSystemId,
|
|
||||||
null,
|
|
||||||
'api',
|
|
||||||
'API User',
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const pid = GBVMService.createProcessInfo(user, min, 'api', null);
|
const pid = GBVMService.createProcessInfo(user, min, 'api', null);
|
||||||
|
|
||||||
const conversation = min['apiConversations'][pid];
|
const conversation = min['apiConversations'][pid];
|
||||||
|
@ -1406,11 +1361,8 @@ export class DialogKeywords {
|
||||||
conversation.conversationId = response.obj.conversationId;
|
conversation.conversationId = response.obj.conversationId;
|
||||||
|
|
||||||
return await GBVMService.callVM('start', min, null, pid);
|
return await GBVMService.callVM('start', min, null, pid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static async getProcessInfo(pid: number) {
|
public static async getProcessInfo(pid: number) {
|
||||||
const proc = GBServer.globals.processes[pid];
|
const proc = GBServer.globals.processes[pid];
|
||||||
const step = proc.step;
|
const step = proc.step;
|
||||||
|
@ -1439,15 +1391,13 @@ export class DialogKeywords {
|
||||||
text = await min.conversationalService.translate(
|
text = await min.conversationalService.translate(
|
||||||
min,
|
min,
|
||||||
text,
|
text,
|
||||||
user.locale ? user.locale : min.core.getParam(min.instance, 'Locale',
|
user.locale ? user.locale : min.core.getParam(min.instance, 'Locale', GBConfigService.get('LOCALE'))
|
||||||
GBConfigService.get('LOCALE'))
|
|
||||||
);
|
);
|
||||||
GBLog.verbose(`Translated text(playMarkdown): ${text}.`);
|
GBLog.verbose(`Translated text(playMarkdown): ${text}.`);
|
||||||
|
|
||||||
if (step) {
|
if (step) {
|
||||||
await min.conversationalService.sendText(min, step, text);
|
await min.conversationalService.sendText(min, step, text);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
await min.conversationalService['sendOnConversation'](min, user, text);
|
await min.conversationalService['sendOnConversation'](min, user, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1484,7 +1434,6 @@ export class DialogKeywords {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GBFILE object.
|
// GBFILE object.
|
||||||
|
|
||||||
else if (filename.url) {
|
else if (filename.url) {
|
||||||
url = filename.url;
|
url = filename.url;
|
||||||
nameOnly = Path.basename(filename.localName);
|
nameOnly = Path.basename(filename.localName);
|
||||||
|
@ -1493,9 +1442,7 @@ export class DialogKeywords {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles Markdown.
|
// Handles Markdown.
|
||||||
|
|
||||||
else if (filename.indexOf('.md') !== -1) {
|
else if (filename.indexOf('.md') !== -1) {
|
||||||
|
|
||||||
GBLogEx.info(min, `BASIC: Sending the contents of ${filename} markdown to mobile ${mobile}.`);
|
GBLogEx.info(min, `BASIC: Sending the contents of ${filename} markdown to mobile ${mobile}.`);
|
||||||
const md = await min.kbService.getAnswerTextByMediaName(min.instance.instanceId, filename);
|
const md = await min.kbService.getAnswerTextByMediaName(min.instance.instanceId, filename);
|
||||||
if (!md) {
|
if (!md) {
|
||||||
|
@ -1504,14 +1451,10 @@ export class DialogKeywords {
|
||||||
await min.conversationalService['playMarkdown'](min, md, DialogKeywords.getChannel(), null, mobile);
|
await min.conversationalService['playMarkdown'](min, md, DialogKeywords.getChannel(), null, mobile);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// .gbdrive direct sending.
|
// .gbdrive direct sending.
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
|
||||||
|
|
||||||
const ext = Path.extname(filename);
|
const ext = Path.extname(filename);
|
||||||
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
const gbaiName = DialogKeywords.getGBAIPath(min.botId);
|
||||||
|
|
||||||
|
@ -1529,15 +1472,18 @@ export class DialogKeywords {
|
||||||
const driveUrl = template['@microsoft.graph.downloadUrl'];
|
const driveUrl = template['@microsoft.graph.downloadUrl'];
|
||||||
const res = await fetch(driveUrl);
|
const res = await fetch(driveUrl);
|
||||||
let buf: any = Buffer.from(await res.arrayBuffer());
|
let buf: any = Buffer.from(await res.arrayBuffer());
|
||||||
let localName1 = Path.join('work', gbaiName, 'cache', `${fileOnly.replace(/\s/gi, '')}-${GBAdminService.getNumberIdentifier()}.${ext}`);
|
let localName1 = Path.join(
|
||||||
|
'work',
|
||||||
|
gbaiName,
|
||||||
|
'cache',
|
||||||
|
`${fileOnly.replace(/\s/gi, '')}-${GBAdminService.getNumberIdentifier()}.${ext}`
|
||||||
|
);
|
||||||
Fs.writeFileSync(localName1, buf, { encoding: null });
|
Fs.writeFileSync(localName1, buf, { encoding: null });
|
||||||
|
|
||||||
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName1));
|
url = urlJoin(GBServer.globals.publicAddress, min.botId, 'cache', Path.basename(localName1));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
|
||||||
const ext = Path.extname(filename.localName);
|
const ext = Path.extname(filename.localName);
|
||||||
|
|
||||||
// Prepare a cache to be referenced by Bot Framework.
|
// Prepare a cache to be referenced by Bot Framework.
|
||||||
|
|
|
@ -128,6 +128,7 @@ export class KeywordsExpressions {
|
||||||
|
|
||||||
keywords[i++] = [/^\s*REM.*/gim, ''];
|
keywords[i++] = [/^\s*REM.*/gim, ''];
|
||||||
|
|
||||||
|
|
||||||
keywords[i++] = [/^\s*CLOSE.*/gim, ''];
|
keywords[i++] = [/^\s*CLOSE.*/gim, ''];
|
||||||
|
|
||||||
// Always autoclose keyword.
|
// Always autoclose keyword.
|
||||||
|
|
|
@ -914,11 +914,28 @@ export class SystemKeywords {
|
||||||
|
|
||||||
body.values[0][filter ? index + 1 : index] = value;
|
body.values[0][filter ? index + 1 : index] = value;
|
||||||
}
|
}
|
||||||
await client
|
|
||||||
.api(
|
await retry(
|
||||||
`${baseUrl}/drive/items/${document.id}/workbook/worksheets('${sheets.value[0].name}')/range(address='${address}')`
|
async bail => {
|
||||||
)
|
const result = await client
|
||||||
.patch(body);
|
.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);
|
||||||
|
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
retries: 5,
|
||||||
|
onRetry: err => {
|
||||||
|
GBLog.error(`Retrying HTTP GET due to: ${err.message}.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1763,7 +1780,7 @@ export class SystemKeywords {
|
||||||
result = await fetch(url, options);
|
result = await fetch(url, options);
|
||||||
|
|
||||||
if (result.status === 401) {
|
if (result.status === 401) {
|
||||||
GBLogEx.info(min, `Waiting 5 secs. before retrynig HTTP 401 GET: ${url}`);
|
GBLogEx.info(min, `Waiting 5 secs. before retrying HTTP 401 GET: ${url}`);
|
||||||
await GBUtil.sleep(5 * 1000);
|
await GBUtil.sleep(5 * 1000);
|
||||||
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
||||||
}
|
}
|
||||||
|
@ -1773,7 +1790,7 @@ export class SystemKeywords {
|
||||||
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
||||||
}
|
}
|
||||||
if (result.status === 503) {
|
if (result.status === 503) {
|
||||||
GBLogEx.info(min, `Waiting 1h before retrynig GET 503: ${url}`);
|
GBLogEx.info(min, `Waiting 1h before retrying GET 503: ${url}`);
|
||||||
await GBUtil.sleep(60 * 60 * 1000);
|
await GBUtil.sleep(60 * 60 * 1000);
|
||||||
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
throw new Error(`BASIC: HTTP:${result.status} retry: ${result.statusText}.`);
|
||||||
}
|
}
|
||||||
|
@ -2679,7 +2696,7 @@ export class SystemKeywords {
|
||||||
let buf: any = Buffer.from(await res.arrayBuffer());
|
let buf: any = Buffer.from(await res.arrayBuffer());
|
||||||
const data = new Uint8Array(buf);
|
const data = new Uint8Array(buf);
|
||||||
const pdf = await getDocument({ data }).promise;
|
const pdf = await getDocument({ data }).promise;
|
||||||
let pages = []
|
let pages = [];
|
||||||
|
|
||||||
for (let i = 1; i <= pdf.numPages; i++) {
|
for (let i = 1; i <= pdf.numPages; i++) {
|
||||||
const page = await pdf.getPage(i);
|
const page = await pdf.getPage(i);
|
||||||
|
@ -2688,19 +2705,20 @@ export class SystemKeywords {
|
||||||
.map(item => item['str'])
|
.map(item => item['str'])
|
||||||
.join('')
|
.join('')
|
||||||
.replace(/\s/g, '');
|
.replace(/\s/g, '');
|
||||||
pages.push(text)
|
pages.push(text);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages.join("");
|
return pages.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setContext({pid, text}){
|
public async setContext({ pid, text }) {
|
||||||
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
||||||
ChatServices.userSystemPrompt[user.userSystemId] = text;
|
ChatServices.userSystemPrompt[user.userSystemId] = text;
|
||||||
|
|
||||||
|
await this.setMemoryContext({ pid, erase: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setMemoryContext({pid, input, output, erase}){
|
public async setMemoryContext({ pid, input = null, output = null, erase }) {
|
||||||
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
const { min, user, params } = await DialogKeywords.getProcessInfo(pid);
|
||||||
let memory;
|
let memory;
|
||||||
if (erase || !ChatServices.memoryMap[user.userSystemId]) {
|
if (erase || !ChatServices.memoryMap[user.userSystemId]) {
|
||||||
|
@ -2716,18 +2734,14 @@ export class SystemKeywords {
|
||||||
memory = ChatServices.memoryMap[user.userSystemId];
|
memory = ChatServices.memoryMap[user.userSystemId];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memory)
|
if (memory && input)
|
||||||
await memory.saveContext(
|
await memory.saveContext(
|
||||||
{
|
{
|
||||||
input: input
|
input: input
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
output: output
|
output: output
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -641,7 +641,12 @@ export class GBConversationalService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fillAndBroadcastTemplate(min: GBMinInstance, mobile: string, text) {
|
|
||||||
|
public async fillAndBroadcastTemplate(min: GBMinInstance, template, mobile: string, text) {
|
||||||
|
|
||||||
|
template = template.replace(/\-/gi, '_')
|
||||||
|
template = template.replace(/\./gi, '_')
|
||||||
|
|
||||||
let isMedia =
|
let isMedia =
|
||||||
text.toLowerCase().endsWith('.jpg') ||
|
text.toLowerCase().endsWith('.jpg') ||
|
||||||
text.toLowerCase().endsWith('.jpeg') ||
|
text.toLowerCase().endsWith('.jpeg') ||
|
||||||
|
@ -659,7 +664,7 @@ export class GBConversationalService {
|
||||||
text = text.replace(/\n/g, '\\n');
|
text = text.replace(/\n/g, '\\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
let template = isMedia ? image.replace(/\.[^/.]+$/, '') : 'broadcast1';
|
template = isMedia ? image.replace(/\.[^/.]+$/, '') : template;
|
||||||
|
|
||||||
let data: any = {
|
let data: any = {
|
||||||
name: template,
|
name: template,
|
||||||
|
@ -678,17 +683,6 @@ export class GBConversationalService {
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isMedia) {
|
|
||||||
data.components.push({
|
|
||||||
type: 'body',
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: text
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.sendToMobile(min, mobile, data, null);
|
await this.sendToMobile(min, mobile, data, null);
|
||||||
GBLogEx.info(min, `Sending answer file to mobile: ${mobile}. Header: ${urlImage}`);
|
GBLogEx.info(min, `Sending answer file to mobile: ${mobile}. Header: ${urlImage}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
|
|
||||||
import Swagger from 'swagger-client';
|
import Swagger from 'swagger-client';
|
||||||
import { google } from 'googleapis';
|
import { google } from 'googleapis';
|
||||||
import { promisify } from 'util';
|
|
||||||
import { PubSub } from '@google-cloud/pubsub';
|
import { PubSub } from '@google-cloud/pubsub';
|
||||||
import Fs from 'fs';
|
import Fs from 'fs';
|
||||||
import { GBLog, GBMinInstance, GBService } from 'botlib';
|
import { GBLog, GBMinInstance, GBService } from 'botlib';
|
||||||
|
|
|
@ -255,7 +255,7 @@ export class ChatServices {
|
||||||
public static memoryMap = {};
|
public static memoryMap = {};
|
||||||
public static userSystemPrompt = {};
|
public static userSystemPrompt = {};
|
||||||
|
|
||||||
public static async answerByGPT(min: GBMinInstance, user, question: string, mode = null) {
|
public static async answerByLLM(min: GBMinInstance, user, question: string, mode = null) {
|
||||||
const answerMode = min.core.getParam(min.instance, 'Answer Mode', null);
|
const answerMode = min.core.getParam(min.instance, 'Answer Mode', null);
|
||||||
|
|
||||||
if (!answerMode || answerMode === 'nollm') {
|
if (!answerMode || answerMode === 'nollm') {
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
* @fileoverview Knowledge base services and logic.
|
* @fileoverview Knowledge base services and logic.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import html2md from 'html-to-md'
|
import html2md from 'html-to-md';
|
||||||
import Path from 'path';
|
import Path from 'path';
|
||||||
import Fs from 'fs';
|
import Fs from 'fs';
|
||||||
import urlJoin from 'url-join';
|
import urlJoin from 'url-join';
|
||||||
|
@ -379,7 +379,7 @@ export class KBService implements IGBKBService {
|
||||||
returnedScore: ${returnedScore} < required (searchScore): ${searchScore}`
|
returnedScore: ${returnedScore} < required (searchScore): ${searchScore}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return await ChatServices.answerByGPT(min, user, query);
|
return await ChatServices.answerByLLM(min, user, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSubjectItems(instanceId: number, parentId: number): Promise<GuaribasSubject[]> {
|
public async getSubjectItems(instanceId: number, parentId: number): Promise<GuaribasSubject[]> {
|
||||||
|
@ -703,7 +703,7 @@ export class KBService implements IGBKBService {
|
||||||
|
|
||||||
// Import remaining .md files in articles directory.
|
// Import remaining .md files in articles directory.
|
||||||
|
|
||||||
await this.importRemainingArticles(localPath, instance, packageStorage.packageId);
|
await this.importRemainingArticles(min, localPath, instance, packageStorage.packageId);
|
||||||
|
|
||||||
// Import docs files in .docx directory.
|
// Import docs files in .docx directory.
|
||||||
|
|
||||||
|
@ -713,7 +713,12 @@ export class KBService implements IGBKBService {
|
||||||
/**
|
/**
|
||||||
* Import all .md files in articles folder that has not been referenced by tabular files.
|
* Import all .md files in articles folder that has not been referenced by tabular files.
|
||||||
*/
|
*/
|
||||||
public async importRemainingArticles(localPath: string, instance: IGBInstance, packageId: number): Promise<any> {
|
public async importRemainingArticles(
|
||||||
|
min: GBMinInstance,
|
||||||
|
localPath: string,
|
||||||
|
instance: IGBInstance,
|
||||||
|
packageId: number
|
||||||
|
): Promise<any> {
|
||||||
const files = await walkPromise(urlJoin(localPath, 'articles'));
|
const files = await walkPromise(urlJoin(localPath, 'articles'));
|
||||||
const data = { questions: [], answers: [] };
|
const data = { questions: [], answers: [] };
|
||||||
|
|
||||||
|
@ -735,14 +740,19 @@ export class KBService implements IGBKBService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (file !== null && file.name.endsWith('.docx')) {
|
} else if (file !== null && file.name.endsWith('.docx')) {
|
||||||
const path = DialogKeywords.getGBAIPath(instance.botId, `gbkb`);
|
let path = DialogKeywords.getGBAIPath(instance.botId, `gbkb`);
|
||||||
const localName = Path.join('work', path, 'articles', file.name);
|
const localName = Path.join('work', path, 'articles', file.name);
|
||||||
let loader = new DocxLoader(localName);
|
let loader = new DocxLoader(localName);
|
||||||
let doc = await loader.load();
|
let doc = await loader.load();
|
||||||
|
let content = doc[0].pageContent;
|
||||||
|
|
||||||
const answer = {
|
if (file.name.endsWith('zap.docx')){
|
||||||
|
await min.whatsAppDirectLine.createOrUpdateTemplate(min, file.name, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
const answer = {
|
||||||
instanceId: instance.instanceId,
|
instanceId: instance.instanceId,
|
||||||
content: doc[0].pageContent,
|
content: content,
|
||||||
format: '.md',
|
format: '.md',
|
||||||
media: file.name,
|
media: file.name,
|
||||||
packageId: packageId,
|
packageId: packageId,
|
||||||
|
@ -923,7 +933,7 @@ export class KBService implements IGBKBService {
|
||||||
// Check if urlToCheck contains any of the ignored URLs
|
// Check if urlToCheck contains any of the ignored URLs
|
||||||
|
|
||||||
var isIgnored = false;
|
var isIgnored = false;
|
||||||
if (websiteIgnoreUrls){
|
if (websiteIgnoreUrls) {
|
||||||
websiteIgnoreUrls.split(';').some(ignoredUrl => p.href.includes(ignoredUrl));
|
websiteIgnoreUrls.split(';').some(ignoredUrl => p.href.includes(ignoredUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -973,22 +983,26 @@ export class KBService implements IGBKBService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLogoByPage(page) {
|
async getLogoByPage(min, page) {
|
||||||
const checkPossibilities = async (page, possibilities) => {
|
const checkPossibilities = async (page, possibilities) => {
|
||||||
for (const possibility of possibilities) {
|
try {
|
||||||
const { tag, attributes } = possibility;
|
for (const possibility of possibilities) {
|
||||||
|
const { tag, attributes } = possibility;
|
||||||
|
|
||||||
for (const attribute of attributes) {
|
for (const attribute of attributes) {
|
||||||
const selector = `${tag}[${attribute}*="logo"]`;
|
const selector = `${tag}[${attribute}*="logo"]`;
|
||||||
const elements = await page.$$(selector);
|
const elements = await page.$$(selector);
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
const src = await page.evaluate(el => el.getAttribute('src'), element);
|
const src = await page.evaluate(el => el.getAttribute('src'), element);
|
||||||
if (src) {
|
if (src) {
|
||||||
return src.split('?')[0];
|
return src.split('?')[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await GBLogEx.info(min, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -1030,13 +1044,13 @@ export class KBService implements IGBKBService {
|
||||||
let files = [];
|
let files = [];
|
||||||
|
|
||||||
let website = min.core.getParam<string>(min.instance, 'Website', null);
|
let website = min.core.getParam<string>(min.instance, 'Website', null);
|
||||||
const maxDepth = min.core.getParam<number>(min.instance, 'Website Depth', 1);
|
const maxDepth = min.core.getParam<number>(min.instance, 'Website Depth', 1);
|
||||||
const websiteIgnoreUrls = min.core.getParam<[]>(min.instance, 'Website Ignore URLs', null);
|
const websiteIgnoreUrls = min.core.getParam<[]>(min.instance, 'Website Ignore URLs', null);
|
||||||
|
|
||||||
if (website) {
|
if (website) {
|
||||||
// Removes last slash if any.
|
// Removes last slash if any.
|
||||||
|
|
||||||
website.endsWith('/')?website.substring(0, website.length-1):website;
|
website.endsWith('/') ? website.substring(0, website.length - 1) : website;
|
||||||
|
|
||||||
let path = DialogKeywords.getGBAIPath(min.botId, `gbot`);
|
let path = DialogKeywords.getGBAIPath(min.botId, `gbot`);
|
||||||
const directoryPath = Path.join(process.env.PWD, 'work', path, 'Website');
|
const directoryPath = Path.join(process.env.PWD, 'work', path, 'Website');
|
||||||
|
@ -1045,30 +1059,29 @@ export class KBService implements IGBKBService {
|
||||||
let browser = await puppeteer.launch({ headless: false });
|
let browser = await puppeteer.launch({ headless: false });
|
||||||
const page = await this.getFreshPage(browser, website);
|
const page = await this.getFreshPage(browser, website);
|
||||||
|
|
||||||
let logo = await this.getLogoByPage(page);
|
let logo = await this.getLogoByPage(min, page);
|
||||||
if (logo) {
|
if (logo) {
|
||||||
path = DialogKeywords.getGBAIPath(min.botId);
|
path = DialogKeywords.getGBAIPath(min.botId);
|
||||||
const logoPath = Path.join(process.env.PWD, 'work', path, 'cache');
|
const logoPath = Path.join(process.env.PWD, 'work', path, 'cache');
|
||||||
const baseUrl = page.url().split('/').slice(0, 3).join('/');
|
const baseUrl = page.url().split('/').slice(0, 3).join('/');
|
||||||
logo = logo.startsWith('https') ? logo : urlJoin(baseUrl, logo);
|
logo = logo.startsWith('https') ? logo : urlJoin(baseUrl, logo);
|
||||||
|
|
||||||
try {
|
|
||||||
const logoBinary = await page.goto(logo);
|
|
||||||
const buffer = await logoBinary.buffer();
|
|
||||||
const logoFilename = Path.basename(logo);
|
|
||||||
sharp(buffer)
|
|
||||||
.resize({
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
fit: 'inside', // Resize the image to fit within the specified dimensions
|
|
||||||
withoutEnlargement: true // Don't enlarge the image if its dimensions are already smaller
|
|
||||||
})
|
|
||||||
.toFile(Path.join(logoPath, logoFilename));
|
|
||||||
await min.core['setConfig'](min, 'Logo', logoFilename);
|
|
||||||
} catch (error) {
|
|
||||||
GBLogEx.debug(min, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const logoBinary = await page.goto(logo);
|
||||||
|
const buffer = await logoBinary.buffer();
|
||||||
|
const logoFilename = Path.basename(logo);
|
||||||
|
sharp(buffer)
|
||||||
|
.resize({
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
fit: 'inside', // Resize the image to fit within the specified dimensions
|
||||||
|
withoutEnlargement: true // Don't enlarge the image if its dimensions are already smaller
|
||||||
|
})
|
||||||
|
.toFile(Path.join(logoPath, logoFilename));
|
||||||
|
await min.core['setConfig'](min, 'Logo', logoFilename);
|
||||||
|
} catch (error) {
|
||||||
|
GBLogEx.debug(min, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract dominant colors from the screenshot
|
// Extract dominant colors from the screenshot
|
||||||
|
|
|
@ -46,6 +46,7 @@ import qrcode from 'qrcode-terminal';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { GBSSR } from '../../core.gbapp/services/GBSSR.js';
|
import { GBSSR } from '../../core.gbapp/services/GBSSR.js';
|
||||||
import pkg from 'whatsapp-web.js';
|
import pkg from 'whatsapp-web.js';
|
||||||
|
import fetch, { Response } from 'node-fetch';
|
||||||
import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
|
import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js';
|
||||||
import { ChatServices } from '../../gpt.gblib/services/ChatServices.js';
|
import { ChatServices } from '../../gpt.gblib/services/ChatServices.js';
|
||||||
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
|
import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
|
||||||
|
@ -55,6 +56,8 @@ import twilio from 'twilio';
|
||||||
import { GBVMService } from '../../basic.gblib/services/GBVMService.js';
|
import { GBVMService } from '../../basic.gblib/services/GBVMService.js';
|
||||||
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
|
||||||
import { createBot } from 'whatsapp-cloud-api';
|
import { createBot } from 'whatsapp-cloud-api';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
const stat = promisify(Fs.stat);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for Whatsapp.
|
* Support for Whatsapp.
|
||||||
|
@ -77,7 +80,10 @@ export class WhatsappDirectLine extends GBService {
|
||||||
public whatsappServiceKey: string;
|
public whatsappServiceKey: string;
|
||||||
public whatsappServiceNumber: string;
|
public whatsappServiceNumber: string;
|
||||||
public whatsappServiceUrl: string;
|
public whatsappServiceUrl: string;
|
||||||
|
public whatsappBusinessManagerId: string;
|
||||||
|
public whatsappFBAppId: string;
|
||||||
public botId: string;
|
public botId: string;
|
||||||
|
public botNumber: string;
|
||||||
public min: GBMinInstance;
|
public min: GBMinInstance;
|
||||||
private directLineSecret: string;
|
private directLineSecret: string;
|
||||||
private locale: string = 'pt-BR';
|
private locale: string = 'pt-BR';
|
||||||
|
@ -126,6 +132,26 @@ export class WhatsappDirectLine extends GBService {
|
||||||
let options: any;
|
let options: any;
|
||||||
|
|
||||||
switch (this.provider) {
|
switch (this.provider) {
|
||||||
|
case 'meta':
|
||||||
|
this.botNumber = this.min.core.getParam<string>(this.min.instance, 'Bot Number', null);
|
||||||
|
let whatsappServiceNumber, whatsappServiceKey, url;
|
||||||
|
if (this.botNumber && this.min.instance.whatsappServiceNumber) {
|
||||||
|
whatsappServiceNumber = this.min.instance.whatsappServiceNumber;
|
||||||
|
whatsappServiceKey = this.min.instance.whatsappServiceKey;
|
||||||
|
url = this.min.instance.whatsappServiceUrl;
|
||||||
|
} else {
|
||||||
|
whatsappServiceNumber = GBServer.globals.minBoot.instance.whatsappServiceNumber;
|
||||||
|
whatsappServiceKey = GBServer.globals.minBoot.instance.whatsappServiceKey;
|
||||||
|
url = GBServer.globals.minBoot.instance.whatsappServiceUrl;
|
||||||
|
}
|
||||||
|
if (url) {
|
||||||
|
const parts = url.split(';');
|
||||||
|
this.whatsappBusinessManagerId = parts[0];
|
||||||
|
this.whatsappFBAppId = parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.customClient = createBot(whatsappServiceNumber, whatsappServiceKey);
|
||||||
|
break;
|
||||||
case 'official':
|
case 'official':
|
||||||
const accountSid = process.env.TWILIO_ACCOUNT_SID;
|
const accountSid = process.env.TWILIO_ACCOUNT_SID;
|
||||||
const authToken = process.env.TWILIO_AUTH_TOKEN;
|
const authToken = process.env.TWILIO_AUTH_TOKEN;
|
||||||
|
@ -381,10 +407,9 @@ export class WhatsappDirectLine extends GBService {
|
||||||
|
|
||||||
// Ignore group messages without the mention to Bot.
|
// Ignore group messages without the mention to Bot.
|
||||||
|
|
||||||
let botNumber = this.min.core.getParam<string>(this.min.instance, 'Bot Number', null);
|
if (this.botNumber && !answerText && !found) {
|
||||||
if (botNumber && !answerText && !found) {
|
let n = this.botNumber.replace('+', '');
|
||||||
botNumber = botNumber.replace('+', '');
|
if (!message.body.startsWith('@' + n)) {
|
||||||
if (!message.body.startsWith('@' + botNumber)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,6 +756,119 @@ export class WhatsappDirectLine extends GBService {
|
||||||
await this.sendFileToDevice(to, url, 'Audio', msg, chatId);
|
await this.sendFileToDevice(to, url, 'Audio', msg, chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to create or update a template using WhatsApp Business API
|
||||||
|
|
||||||
|
public async createOrUpdateTemplate(min: GBMinInstance, template, text) {
|
||||||
|
|
||||||
|
template = template.replace(/\-/gi, '_')
|
||||||
|
template = template.replace(/\./gi, '_')
|
||||||
|
|
||||||
|
let image = /(.*)\n/gim.exec(text)[0].trim();
|
||||||
|
|
||||||
|
let path = DialogKeywords.getGBAIPath(min.botId, `gbkb`);
|
||||||
|
path = Path.join(process.env.PWD, 'work', path, 'images', image);
|
||||||
|
|
||||||
|
text = text.substring(image.length + 1).trim();
|
||||||
|
text = text.replace(/\n/g, '\\n');
|
||||||
|
|
||||||
|
const handleImage = await min.whatsAppDirectLine.uploadLargeFile(min, path);
|
||||||
|
|
||||||
|
let data: any = {
|
||||||
|
name: template,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: 'HEADER',
|
||||||
|
format: 'IMAGE',
|
||||||
|
example: { header_handle: [handleImage] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'BODY',
|
||||||
|
text: text
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const name = data.name;
|
||||||
|
|
||||||
|
// Define the API base URL and endpoints
|
||||||
|
|
||||||
|
const baseUrl = 'https://graph.facebook.com/v20.0'; // API version 20.0
|
||||||
|
const businessAccountId = this.whatsappBusinessManagerId;
|
||||||
|
const accessToken = this.whatsappServiceKey;
|
||||||
|
|
||||||
|
// Endpoint for listing templates
|
||||||
|
|
||||||
|
const listTemplatesEndpoint = `${baseUrl}/${businessAccountId}/message_templates?access_token=${accessToken}`;
|
||||||
|
|
||||||
|
// Step 1: Check if the template exists
|
||||||
|
|
||||||
|
const listResponse = await fetch(listTemplatesEndpoint, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!listResponse.ok) {
|
||||||
|
throw new Error('Failed to list templates');
|
||||||
|
}
|
||||||
|
|
||||||
|
const templates = await listResponse.json();
|
||||||
|
const templateExists = templates.data.find(template => template.name === name);
|
||||||
|
|
||||||
|
if (templateExists) {
|
||||||
|
// Step 2: Update the template
|
||||||
|
const updateTemplateEndpoint = `${baseUrl}/${templateExists.id}`;
|
||||||
|
|
||||||
|
// Removes the first HEADER element.
|
||||||
|
|
||||||
|
data.components.shift();
|
||||||
|
|
||||||
|
const updateResponse = await fetch(updateTemplateEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
components: data.components
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!updateResponse.ok) {
|
||||||
|
throw new Error(`Failed to update template: ${name} ${await updateResponse.text()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
GBLogEx.info(min, `Template updated: ${name}`);
|
||||||
|
} else {
|
||||||
|
// Step 3: Create the template
|
||||||
|
const createTemplateEndpoint = `${baseUrl}/${businessAccountId}/message_templates`;
|
||||||
|
|
||||||
|
const createResponse = await fetch(createTemplateEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: data['name'],
|
||||||
|
language: 'pt_BR',
|
||||||
|
category: 'MARKETING',
|
||||||
|
components: data.components
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!createResponse.ok) {
|
||||||
|
const body = await createResponse.text();
|
||||||
|
throw new Error(`Failed to create template: ${name} ${body}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
GBLogEx.info(min, `Template created: ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await GBUtil.sleep(20 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
public async sendToDevice(to: any, msg: string, conversationId) {
|
public async sendToDevice(to: any, msg: string, conversationId) {
|
||||||
try {
|
try {
|
||||||
const cmd = '/audio ';
|
const cmd = '/audio ';
|
||||||
|
@ -747,26 +885,17 @@ export class WhatsappDirectLine extends GBService {
|
||||||
|
|
||||||
switch (this.provider) {
|
switch (this.provider) {
|
||||||
case 'meta':
|
case 'meta':
|
||||||
let whatsappServiceNumber, whatsappServiceKey;
|
|
||||||
if (botNumber && this.min.instance.whatsappServiceNumber) {
|
|
||||||
whatsappServiceNumber = this.min.instance.whatsappServiceNumber;
|
|
||||||
whatsappServiceKey = this.min.instance.whatsappServiceKey;
|
|
||||||
} else {
|
|
||||||
whatsappServiceNumber = GBServer.globals.minBoot.instance.whatsappServiceNumber;
|
|
||||||
whatsappServiceKey = GBServer.globals.minBoot.instance.whatsappServiceKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
const driver = createBot(whatsappServiceNumber, whatsappServiceKey);
|
|
||||||
|
|
||||||
if (msg['name']) {
|
if (msg['name']) {
|
||||||
const res = await driver.sendTemplate(to, msg['name'], 'pt_BR', msg['components']);
|
await this.customClient.sendTemplate(to, msg['name'], 'pt_BR', msg['components']);
|
||||||
} else {
|
} else {
|
||||||
messages = msg.match(/(.|[\r\n]){1,4096}/g);
|
messages = msg.match(/(.|[\r\n]){1,4096}/g);
|
||||||
|
|
||||||
await CollectionUtil.asyncForEach(messages, async msg => {
|
await CollectionUtil.asyncForEach(messages, async msg => {
|
||||||
await driver.sendText(to, msg);
|
await this.customClient.sendText(to, msg);
|
||||||
|
|
||||||
await GBUtil.sleep(3000);
|
if (messages.length > 1) {
|
||||||
|
await GBUtil.sleep(3000);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1092,4 +1221,82 @@ export class WhatsappDirectLine extends GBService {
|
||||||
GBLog.error(`Error on Whatsapp callback: ${GBUtil.toYAML(error)}`);
|
GBLog.error(`Error on Whatsapp callback: ${GBUtil.toYAML(error)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async uploadLargeFile(min, filePath) {
|
||||||
|
const CHUNK_SIZE = 4 * 1024 * 1024; // 4MB chunks
|
||||||
|
let uploadSessionId;
|
||||||
|
const fileSize = (await stat(filePath)).size;
|
||||||
|
const fileName = filePath.split('/').pop();
|
||||||
|
const fileType = mime.lookup(filePath);
|
||||||
|
const appId = this.whatsappFBAppId;
|
||||||
|
const userAccessToken = this.whatsappServiceKey;
|
||||||
|
let h;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fileType) {
|
||||||
|
throw new Error('Unsupported file type');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Start an upload session
|
||||||
|
const startResponse = await fetch(
|
||||||
|
`https://graph.facebook.com/v20.0/${appId}/uploads?file_name=${fileName}&file_length=${fileSize}&file_type=${fileType}&access_token=${userAccessToken}`,
|
||||||
|
{
|
||||||
|
method: 'POST'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const startData = await startResponse.json();
|
||||||
|
if (!startResponse.ok) {
|
||||||
|
throw new Error(startData.error.message);
|
||||||
|
}
|
||||||
|
uploadSessionId = startData.id.split(':')[1];
|
||||||
|
|
||||||
|
// Step 2: Upload the file in chunks
|
||||||
|
let startOffset = 0;
|
||||||
|
|
||||||
|
while (startOffset < fileSize) {
|
||||||
|
const endOffset = Math.min(startOffset + CHUNK_SIZE, fileSize);
|
||||||
|
const fileStream = Fs.createReadStream(filePath, { start: startOffset, end: endOffset - 1 });
|
||||||
|
const chunkSize = endOffset - startOffset;
|
||||||
|
|
||||||
|
const uploadResponse = await fetch(`https://graph.facebook.com/v20.0/upload:${uploadSessionId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `OAuth ${userAccessToken}`,
|
||||||
|
file_offset: startOffset.toString(),
|
||||||
|
'Content-Length': chunkSize.toString()
|
||||||
|
},
|
||||||
|
body: fileStream
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadData = await uploadResponse.json();
|
||||||
|
if (!h) {
|
||||||
|
h = uploadData.h;
|
||||||
|
}
|
||||||
|
if (!uploadResponse.ok) {
|
||||||
|
throw new Error(uploadData.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
startOffset = endOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Get the file handle
|
||||||
|
const finalizeResponse = await fetch(`https://graph.facebook.com/v20.0/upload:${uploadSessionId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
Authorization: `OAuth ${userAccessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalizeData = await finalizeResponse.json();
|
||||||
|
if (!finalizeResponse.ok) {
|
||||||
|
throw new Error(finalizeData.error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Upload completed successfully with file handle:', finalizeData.h);
|
||||||
|
return h;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during file upload:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,11 +117,11 @@ export class GBServer {
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('uncaughtException', (err, p) => {
|
process.on('uncaughtException', (err, p) => {
|
||||||
GBLogEx.error(0, `GBEXCEPTION: ${GBUtil.toYAML(JSON.stringify(err, Object.getOwnPropertyNames(err)))}`);
|
GBLogEx.error(0, `GBEXCEPTION: ${GBUtil.toYAML(err)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('unhandledRejection', (err, p) => {
|
process.on('unhandledRejection', (err, p) => {
|
||||||
GBLogEx.error(0,`GBREJECTION: ${GBUtil.toYAML(JSON.stringify(err, Object.getOwnPropertyNames(err)))}`);
|
GBLogEx.error(0,`GBREJECTION: ${GBUtil.toYAML(err)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creates working directory.
|
// Creates working directory.
|
||||||
|
|
Loading…
Add table
Reference in a new issue