diff --git a/package.json b/package.json index 9265b8f8..1a72a454 100644 --- a/package.json +++ b/package.json @@ -214,6 +214,7 @@ "vm2-process": "2.1.1", "walk-promise": "0.2.0", "washyourmouthoutwithsoap": "1.0.2", + "whatsapp-cloud-api": "^0.3.1", "whatsapp-web.js": "https://github.com/Julzk/whatsapp-web.js/tarball/jkr_hotfix_7", "winston": "3.8.2", "ws": "8.14.2", diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 44585943..984b677c 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -442,9 +442,24 @@ export class GBMinService { // Setups official handler for WhatsApp. - GBServer.globals.server.post(`/${min.instance.botId}/whatsapp`, async (req, res) => { - const to = req.body.To.replace(/whatsapp\:\+/gi, ''); - const whatsAppDirectLine = WhatsappDirectLine.botsByNumber[to]; + GBServer.globals.server.all(`/${min.instance.botId}/whatsapp`, async (req, res) => { + + if (req.query['hub.mode'] === 'subscribe') { + const val = req.query['hub.challenge']; + res.send(val); + + return; + } + + let whatsAppDirectLine = min.whatsAppDirectLine; + + // Not meta, multiples bots on root bot. + + if (!req.body.object) { + const to = req.body.To.replace(/whatsapp\:\+/gi, ''); + whatsAppDirectLine = WhatsappDirectLine.botsByNumber[to]; + } + await whatsAppDirectLine.WhatsAppCallback(req, res, whatsAppDirectLine.botId); }).bind(min); @@ -455,6 +470,11 @@ export class GBMinService { if (!res) { return 'GeneralBots'; } + + if (req.body.entry) { + return 'meta'; + } + if (req.body?.AccountSid) { return 'official'; } @@ -637,10 +657,10 @@ export class GBMinService { if (botId === '[default]' || botId === undefined) { botId = GBConfigService.get('BOT_ID'); } - - + + // Loads by the botId itself or by the activationCode field. - + let instance = await this.core.loadInstanceByBotId(botId); if (instance === null) { instance = await this.core.loadInstanceByActivationCode(botId); @@ -1076,7 +1096,7 @@ export class GBMinService { // Required for F0 handling of persisted conversations. - GBLogEx.info(min, + GBLogEx.info(min, `Input> ${context.activity.text} (type: ${context.activity.type}, name: ${context.activity.name}, channelId: ${context.activity.channelId})` ); @@ -1114,7 +1134,7 @@ export class GBMinService { ) { min['conversationWelcomed'][step.context.activity.conversation.id] = true; - GBLogEx.info(min, + GBLogEx.info(min, `Auto start (web 1) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...` ); await GBVMService.callVM(startDialog.toLowerCase(), min, step, pid); @@ -1413,12 +1433,12 @@ export class GBMinService { startDialog && startDialog !== '' && !min['conversationWelcomed'][step.context.activity.conversation.id] && - !min['apiConversations'][pid] && + !min['apiConversations'][pid] && !step.context.activity['group'] ) { await sec.setParam(userId, 'welcomed', 'true'); min['conversationWelcomed'][step.context.activity.conversation.id] = true; - GBLogEx.info(min, + GBLogEx.info(min, `Auto start (4) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...` ); await GBVMService.callVM(startDialog.toLowerCase(), min, step, pid); @@ -1625,26 +1645,25 @@ export class GBMinService { ); let pid = data?.pid; - if (script === 'start'){ + if (script === 'start') { pid = GBVMService.createProcessInfo(user, min, 'api', null); - - - const client = await new SwaggerClient({ - spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')), - requestInterceptor: req => { - req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`; - } - }); - const response = await client.apis.Conversations.Conversations_StartConversation(); - - min['apiConversations'][pid] = {conversation: response.obj, client: client}; - min['conversationWelcomed'][response.obj.id] = true; - } - let ret = await GBVMService.callVM(script, min, null, pid, false, data); - if (script === 'start') - { + const client = await new SwaggerClient({ + spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')), + requestInterceptor: req => { + req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`; + } + }); + const response = await client.apis.Conversations.Conversations_StartConversation(); + + min['apiConversations'][pid] = { conversation: response.obj, client: client }; + min['conversationWelcomed'][response.obj.id] = true; + } + + let ret = await GBVMService.callVM(script, min, null, pid, false, data); + + if (script === 'start') { ret = pid; } return ret; @@ -1680,7 +1699,7 @@ export class GBMinService { }; GBServer.globals.server.apiServer = createKoaHttpServer( - GBVMService.API_PORT, + GBVMService.API_PORT, getRemoteId, { prefix: `api/v3` }); createRpcServer( diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index e43fdcac..cb79e979 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -54,7 +54,7 @@ const { WAState, Client, MessageMedia } = pkg; import twilio from 'twilio'; import { GBVMService } from '../../basic.gblib/services/GBVMService.js'; import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js'; - +import { createBot } from 'whatsapp-cloud-api'; /** * Support for Whatsapp. @@ -103,9 +103,8 @@ export class WhatsappDirectLine extends GBService { this.whatsappServiceKey = whatsappServiceKey; this.whatsappServiceNumber = whatsappServiceNumber; this.whatsappServiceUrl = whatsappServiceUrl; - this.provider = - whatsappServiceKey === 'internal' - ? 'GeneralBots' : 'official'; + this.provider = whatsappServiceKey === 'internal' + ? 'GeneralBots' : 'meta'; this.groupId = groupId; } @@ -253,12 +252,8 @@ export class WhatsappDirectLine extends GBService { } } - public static providerFromRequest(req: any) { - return req.body.ProfileName ? 'official' : 'GeneralBots'; - } - public async received(req, res) { - const provider = WhatsappDirectLine.providerFromRequest(req); + const provider = GBMinService.getProviderName(req, res); let message, to, from, fromName, text: string; let group = ''; @@ -266,7 +261,13 @@ export class WhatsappDirectLine extends GBService { let attachments = null; switch (provider) { - + case 'meta': + from = req.body.entry[0].changes[0].value.messages[0].from; + text = req.body.entry[0].changes[0].value.messages[0].text.body + to = this.min.core.getParam(this.min.instance, 'Bot Number', null); + fromName = req.body.entry[0].changes[0].value.contacts[0].profile.name; + + break; case 'official': message = req.body; from = req.body.From.replace(/whatsapp\:\+/gi, ''); @@ -512,7 +513,7 @@ export class WhatsappDirectLine extends GBService { } WhatsappDirectLine.mobiles[generatedConversationId] = from; WhatsappDirectLine.usernames[from] = fromName; - WhatsappDirectLine.chatIds[generatedConversationId] = message.chatId; + WhatsappDirectLine.chatIds[generatedConversationId] = message?.chatId; this.pollMessages(client, generatedConversationId, from, fromName); this.inputMessage(client, generatedConversationId, text, from, fromName, group, attachments); @@ -594,7 +595,8 @@ export class WhatsappDirectLine extends GBService { await this.printMessages(response.obj.activities, conversationId, from, fromName); } catch (err) { GBLog.error( - `Error calling printMessages on Whatsapp channel ${err.data === undefined ? err : err.data} ${err.errObj ? err.errObj.message : '' + `Error calling printMessages on Whatsapp channel ${err.data === undefined ? + err : err.data} ${err.errObj ? err.errObj.message : '' }` ); } @@ -718,17 +720,27 @@ export class WhatsappDirectLine extends GBService { return await this.sendTextAsAudioToDevice(to, msg, chatId); } else { - let options; + let options, messages; + const botNumber = this.min.core.getParam(this.min.instance, 'Bot Number', null); switch (this.provider) { + case 'meta': + const bot = createBot(this.min.instance.whatsappServiceNumber, + this.min.instance.whatsappServiceKey); + messages = msg.match(/(.|[\r\n]){1,4096}/g) + await CollectionUtil.asyncForEach(messages, async msg => { + await GBUtil.sleep(3000); + await bot.sendText(to, msg); + }); + + break; case 'official': - const botNumber = this.min.core.getParam(this.min.instance, 'Bot Number', null); if (to.charAt(0) !== '+') { to = `+${to}` } - let messages = msg.match(/(.|[\r\n]){1,1000}/g) + messages = msg.match(/(.|[\r\n]){1,1000}/g) await CollectionUtil.asyncForEach(messages, async msg => { await GBUtil.sleep(3000); @@ -793,6 +805,19 @@ export class WhatsappDirectLine extends GBService { switch (provider) { + case 'meta': + if (req.body.entry[0].changes[0].value.statuses){ + GBLogEx.info(this.min, `WhatsApp: ${req.body.entry[0].changes[0].value.statuses[0].status}.`); + + return; + } + id = req.body.entry[0].changes[0].value.messages[0].from; + text = req.body.entry[0].changes[0].value.messages[0].text.body + senderName = req.body.entry[0].changes[0].value.contacts[0].profile.name; + botId = this.botId; + + break; + case 'official': const { body } = req;