"use strict";
/*****************************************************************************\
|                                               ( )_  _                       |
|    _ _    _ __   _ _    __    ___ ___     _ _ | ,_)(_)  ___   ___     _     |
|   ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| |  | |/',__)/' v `\ /'_`\   |
|   | (_) )| |  ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) )  |
|   | ,__/'(_)  `\__,_)`\__  |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/'  |
|   | |                ( )_) |                                                |
|   (_)                 \___/'                                                |
|                                                                             |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved.             |
| Licensed under the AGPL-3.0.                                                |
|                                                                             |
| 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,             |
| but WITHOUT ANY WARRANTY, without even the implied warranty of              |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                |
| GNU Affero General Public License for more details.                         |
|                                                                             |
| "General Bots" is a registered trademark of Pragmatismo.io.                 |
| 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.                                     |
|                                                                             |
\*****************************************************************************/
Object.defineProperty(exports, "__esModule", { value: true });
exports.WhatsappDirectLine = void 0;
const urlJoin = require("url-join");
const Swagger = require('swagger-client');
const rp = require('request-promise');
const fs = require('fs');
const botlib_1 = require("botlib");
const pragmatismo_io_framework_1 = require("pragmatismo-io-framework");
const request = require("request-promise-native");
const app_1 = require("../../../src/app");
const GBConversationalService_1 = require("../../core.gbapp/services/GBConversationalService");
const SecService_1 = require("../../security.gbapp/services/SecService");
const strings_1 = require("../strings");
/**
 * Support for Whatsapp.
 */
class WhatsappDirectLine extends botlib_1.GBService {
    constructor(min, botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber, whatsappServiceUrl) {
        super();
        this.pollInterval = 5000;
        this.directLineClientName = 'DirectLineClient';
        this.locale = 'pt-BR';
        this.min = min;
        this.botId = botId;
        this.directLineSecret = directLineSecret;
        this.whatsappServiceKey = whatsappServiceKey;
        this.whatsappServiceNumber = whatsappServiceNumber;
        this.whatsappServiceUrl = whatsappServiceUrl;
    }
    static async asyncForEach(array, callback) {
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index, array);
        }
    }
    async setup(setUrl) {
        this.directLineClient =
            new Swagger({
                spec: JSON.parse(fs.readFileSync('directline-3.0.json', 'utf8')),
                usePromise: true
            });
        const client = await this.directLineClient;
        client.clientAuthorizations.add('AuthorizationBotConnector', new Swagger.ApiKeyAuthorization('Authorization', `Bearer ${this.directLineSecret}`, 'header'));
        const options = {
            method: 'POST',
            url: urlJoin(this.whatsappServiceUrl, 'webhook'),
            timeout: 10000,
            qs: {
                token: this.whatsappServiceKey,
                webhookUrl: `${app_1.GBServer.globals.publicAddress}/webhooks/whatsapp`,
                set: true
            },
            headers: {
                'cache-control': 'no-cache'
            }
        };
        if (setUrl) {
            const express = require('express');
            app_1.GBServer.globals.server.use(`/audios`, express.static('work'));
            if (process.env.ENDPOINT_UPDATE === 'true') {
                try {
                    const res = await request.post(options);
                    botlib_1.GBLog.info(res);
                }
                catch (error) {
                    botlib_1.GBLog.error(`Error initializing 3rd party Whatsapp provider(1) ${error.message}`);
                }
            }
        }
    }
    async resetConversationId(number) {
        WhatsappDirectLine.conversationIds[number] = undefined;
    }
    async check() {
        botlib_1.GBLog.info(`GBWhatsapp: Checking server...`);
        const options = {
            url: urlJoin(this.whatsappServiceUrl, 'status') + `?token=${this.min.instance.whatsappServiceKey}`,
            method: 'GET'
        };
        const res = await request(options);
        const json = JSON.parse(res);
        return json.accountStatus === 'authenticated';
    }
    async received(req, res) {
        if (req.body.messages === undefined) {
            res.end();
            return; // Exit here.
        }
        const message = req.body.messages[0];
        let text = message.body;
        const from = message.author.split('@')[0];
        const fromName = message.senderName;
        if (req.body.messages[0].fromMe) {
            res.end();
            return; // Exit here.
        }
        botlib_1.GBLog.info(`GBWhatsapp: RCV ${from}(${fromName}): ${text})`);
        await pragmatismo_io_framework_1.CollectionUtil.asyncForEach(this.min.appPackages, async (e) => {
            await e.onExchangeData(this.min, 'whatsappMessage', message);
        });
        const id = req.body.messages[0].chatId.split('@')[0].replace('true_', '').replace('false_', '');
        const senderName = req.body.messages[0].senderName;
        const sec = new SecService_1.SecService();
        const user = await sec.ensureUser(this.min.instance.instanceId, id, senderName, '', 'whatsapp', senderName, null);
        const locale = user.locale ? user.locale : 'pt';
        if (message.type === 'ptt') {
            if (process.env.AUDIO_DISABLED !== 'true') {
                const options = {
                    url: message.body,
                    method: 'GET',
                    encoding: 'binary'
                };
                const res = await request(options);
                const buf = Buffer.from(res, 'binary');
                text = await GBConversationalService_1.GBConversationalService.getTextFromAudioBuffer(this.min.instance.speechKey, this.min.instance.cloudLocation, buf, locale);
            }
            else {
                await this.sendToDevice(user.userSystemId, `No momento estou apenas conseguindo ler mensagens de texto.`);
            }
        }
        const conversationId = WhatsappDirectLine.conversationIds[from];
        const client = await this.directLineClient;
        // Check if this message is from a Human Agent itself.
        if (user.agentMode === 'self') {
            // Check if there is someone being handled by this Human Agent.
            const manualUser = await sec.getUserFromAgentSystemId(id);
            if (manualUser === null) {
                await sec.updateHumanAgent(id, this.min.instance.instanceId, null);
            }
            else {
                const agent = await sec.getUserFromSystemId(user.agentSystemId);
                const cmd = '/reply ';
                if (text.startsWith(cmd)) {
                    const filename = text.substr(cmd.length);
                    const message = await this.min.kbService.getAnswerTextByMediaName(this.min.instance.instanceId, filename);
                    if (message === null) {
                        await this.sendToDeviceEx(user.userSystemId, `File ${filename} not found in any .gbkb published. Check the name or publish again the associated .gbkb.`, locale);
                    }
                    else {
                        await this.min.conversationalService.sendMarkdownToMobile(this.min, null, user.userSystemId, message);
                    }
                }
                else if (text === '/qt') {
                    // TODO: Transfers only in pt-br for now.
                    await this.sendToDeviceEx(manualUser.userSystemId, strings_1.Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
                    if (user.agentSystemId.charAt(2) === ":") { // Agent is from Teams.
                        await this.min.conversationalService['sendOnConversation'](this.min, agent, strings_1.Messages[this.locale].notify_end_transfer(this.min.instance.botId));
                    }
                    else {
                        await this.sendToDeviceEx(user.agentSystemId, strings_1.Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
                    }
                    await sec.updateHumanAgent(manualUser.userSystemId, this.min.instance.instanceId, null);
                    await sec.updateHumanAgent(user.agentSystemId, this.min.instance.instanceId, null);
                }
                else {
                    botlib_1.GBLog.info(`HUMAN AGENT (${manualUser.agentSystemId}) TO USER ${manualUser.userSystemId}: ${text}`);
                    await this.sendToDeviceEx(manualUser.userSystemId, `AGENTE: *${text}*`, locale);
                }
            }
        }
        else if (user.agentMode === 'human') {
            const agent = await sec.getUserFromSystemId(user.agentSystemId);
            if (text === '/t') {
                await this.sendToDeviceEx(user.userSystemId, `Você já está sendo atendido por ${agent.userSystemId}.`, locale);
            }
            else if (text === '/qt' || text === 'Sair' || text === 'Fechar') {
                // TODO: Transfers only in pt-br for now.
                await this.sendToDeviceEx(id, strings_1.Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
                if (user.agentSystemId.charAt(2) === ":") { // Agent is from Teams.
                    await this.min.conversationalService['sendOnConversation'](this.min, agent, strings_1.Messages[this.locale].notify_end_transfer(this.min.instance.botId));
                }
                else {
                    await this.sendToDeviceEx(user.agentSystemId, strings_1.Messages[this.locale].notify_end_transfer(this.min.instance.botId), locale);
                }
                await sec.updateHumanAgent(id, this.min.instance.instanceId, null);
            }
            else {
                botlib_1.GBLog.info(`USER (${id}) TO AGENT ${agent.userSystemId}: ${text}`);
                if (user.agentSystemId.charAt(2) === ":" || agent.userSystemId.indexOf("@") > -1) { // Agent is from Teams or Google Chat.
                    await this.min.conversationalService['sendOnConversation'](this.min, agent, text);
                }
                else {
                    await this.sendToDeviceEx(user.agentSystemId, `Bot: ${this.min.instance.botId}\n${id}: ${text}`, locale);
                }
            }
        }
        else if (user.agentMode === 'bot' || user.agentMode === null || user.agentMode === undefined) {
            if (WhatsappDirectLine.conversationIds[from] === undefined) {
                botlib_1.GBLog.info(`GBWhatsapp: Starting new conversation on Bot.`);
                const response = await client.Conversations.Conversations_StartConversation();
                const generatedConversationId = response.obj.conversationId;
                WhatsappDirectLine.conversationIds[from] = generatedConversationId;
                this.pollMessages(client, generatedConversationId, from, fromName);
                this.inputMessage(client, generatedConversationId, text, from, fromName);
            }
            else {
                this.inputMessage(client, conversationId, text, from, fromName);
            }
        }
        else {
            botlib_1.GBLog.warn(`Inconsistencty found: Invalid agentMode on User Table: ${user.agentMode}`);
        }
        res.end();
    }
    inputMessage(client, conversationId, text, from, fromName) {
        return client.Conversations.Conversations_PostActivity({
            conversationId: conversationId,
            activity: {
                textFormat: 'plain',
                text: text,
                type: 'message',
                mobile: from,
                from: {
                    id: from,
                    name: fromName
                },
                replyToId: from
            }
        });
    }
    pollMessages(client, conversationId, from, fromName) {
        botlib_1.GBLog.info(`GBWhatsapp: Starting message polling(${from}, ${conversationId}).`);
        let watermark;
        const worker = async () => {
            try {
                const response = await client.Conversations.Conversations_GetActivities({
                    conversationId: conversationId,
                    watermark: watermark
                });
                watermark = response.obj.watermark;
                await this.printMessages(response.obj.activities, conversationId, from, fromName);
            }
            catch (err) {
                botlib_1.GBLog.error(`Error calling printMessages on Whatsapp channel ${err.data === undefined ? err : err.data}`);
            }
        };
        setInterval(worker, this.pollInterval);
    }
    async printMessages(activities, conversationId, from, fromName) {
        if (activities && activities.length) {
            // Ignore own messages.
            activities = activities.filter(m => m.from.id === this.botId && m.type === 'message');
            if (activities.length) {
                // Print other messages.
                await WhatsappDirectLine.asyncForEach(activities, async (activity) => {
                    await this.printMessage(activity, conversationId, from, fromName);
                });
            }
        }
    }
    async printMessage(activity, conversationId, from, fromName) {
        let output = '';
        if (activity.text) {
            botlib_1.GBLog.info(`GBWhatsapp: SND ${from}(${fromName}): ${activity.text}`);
            output = activity.text;
        }
        if (activity.attachments) {
            activity.attachments.forEach(attachment => {
                switch (attachment.contentType) {
                    case 'application/vnd.microsoft.card.hero':
                        output += `\n${this.renderHeroCard(attachment)}`;
                        break;
                    case 'image/png':
                        botlib_1.GBLog.info(`Opening the requested image ${attachment.contentUrl}`);
                        output += `\n${attachment.contentUrl}`;
                        break;
                    default:
                        botlib_1.GBLog.info(`Unknown content type: ${attachment.contentType}`);
                }
            });
        }
        await this.sendToDevice(from, output);
    }
    renderHeroCard(attachment) {
        return `${attachment.content.title} - ${attachment.content.text}`;
    }
    async sendFileToDevice(to, url, filename, caption) {
        const options = {
            method: 'POST',
            url: urlJoin(this.whatsappServiceUrl, 'sendFile'),
            qs: {
                token: this.whatsappServiceKey,
                phone: to,
                body: url,
                filename: filename,
                caption: caption
            },
            headers: {
                'cache-control': 'no-cache'
            }
        };
        try {
            // tslint:disable-next-line: await-promise
            const result = await request.post(options);
            botlib_1.GBLog.info(`File ${url} sent to ${to}: ${result}`);
        }
        catch (error) {
            botlib_1.GBLog.error(`Error sending file to Whatsapp provider ${error.message}`);
        }
    }
    async sendAudioToDevice(to, url) {
        const options = {
            method: 'POST',
            url: urlJoin(this.whatsappServiceUrl, 'sendPTT'),
            qs: {
                token: this.whatsappServiceKey,
                phone: to,
                body: url
            },
            headers: {
                'cache-control': 'no-cache'
            }
        };
        try {
            // tslint:disable-next-line: await-promise
            const result = await request.post(options);
            botlib_1.GBLog.info(`Audio ${url} sent to ${to}: ${result}`);
        }
        catch (error) {
            botlib_1.GBLog.error(`Error sending audio message to Whatsapp provider ${error.message}`);
        }
    }
    async sendTextAsAudioToDevice(to, msg) {
        // DISABLED:
        // const url = await GBConversationalService.getAudioBufferFromText(
        //   this.min.instance.speechKey,
        //   this.min.instance.cloudLocation,
        //   msg, this.locale
        // );
        const url = 'https://';
        await this.sendFileToDevice(to, url, 'Audio', msg);
    }
    async sendToDevice(to, msg) {
        const cmd = '/audio ';
        if (msg.startsWith(cmd)) {
            msg = msg.substr(cmd.length);
            return await this.sendTextAsAudioToDevice(to, msg);
        }
        else {
            const options = {
                method: 'POST',
                url: urlJoin(this.whatsappServiceUrl, 'message'),
                qs: {
                    token: this.whatsappServiceKey,
                    phone: to,
                    body: msg
                },
                headers: {
                    'cache-control': 'no-cache'
                }
            };
            try {
                // tslint:disable-next-line: await-promise
                const result = await request.post(options);
                botlib_1.GBLog.info(`Message [${msg}] sent to ${to}: ${result}`);
            }
            catch (error) {
                botlib_1.GBLog.error(`Error sending message to Whatsapp provider ${error.message}`);
                // TODO: Handle Error: socket hang up and retry.
            }
        }
    }
    async sendToDeviceEx(to, text, locale) {
        text = await this.min.conversationalService.translate(this.min, text, locale);
        await this.sendToDevice(to, text);
    }
}
exports.WhatsappDirectLine = WhatsappDirectLine;
WhatsappDirectLine.conversationIds = {};
//# sourceMappingURL=WhatsappDirectLine.js.map