From 2a142e3afc8905ff55f098b7c1ac83f12982161f Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Fri, 11 May 2018 22:18:38 -0300 Subject: [PATCH] - NEW: Now each .gbapp has it own set of syspackages loaded. - NEW: Added support for Whatsapp external service key on bot instance model. --- .gitignore | 5 ++ deploy/admin.gbapp/index.ts | 2 + deploy/analytics.gblib/index.ts | 2 + deploy/core.gbapp/index.ts | 3 +- deploy/core.gbapp/models/GBModel.ts | 6 +- deploy/core.gbapp/services/GBMinService.ts | 34 ++++++---- deploy/customer-satisfaction.gbapp/index.ts | 2 +- deploy/kb.gbapp/dialogs/AskDialog.ts | 2 +- deploy/kb.gbapp/index.ts | 3 + deploy/security.gblib/index.ts | 2 +- deploy/whatsapp.gblib/index.ts | 65 ++++++++++++++++++++ deploy/whatsapp.gblib/services/directline.ts | 62 ++++++++++++++----- package.json | 8 ++- src/app.ts | 42 ++++++------- 14 files changed, 182 insertions(+), 56 deletions(-) create mode 100644 deploy/whatsapp.gblib/index.ts diff --git a/.gitignore b/.gitignore index 9a4c83bc..3e608fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,10 @@ node_modules /guaribas.sqlite /guaribas.log /work +/tmp /docs /.env +/chart.html +/chart.png +/chart.svg +/gbtrace.log diff --git a/deploy/admin.gbapp/index.ts b/deploy/admin.gbapp/index.ts index 25f496fc..94c2890e 100644 --- a/deploy/admin.gbapp/index.ts +++ b/deploy/admin.gbapp/index.ts @@ -40,6 +40,8 @@ import { Sequelize } from 'sequelize-typescript'; import { IGBCoreService } from 'botlib'; export class GBAdminPackage implements IGBPackage { + + sysPackages: IGBPackage[] = null; loadPackage(core: IGBCoreService, sequelize: Sequelize): void { } unloadPackage(core: IGBCoreService): void { diff --git a/deploy/analytics.gblib/index.ts b/deploy/analytics.gblib/index.ts index e3489532..c58afd26 100644 --- a/deploy/analytics.gblib/index.ts +++ b/deploy/analytics.gblib/index.ts @@ -42,6 +42,8 @@ import { Sequelize } from "sequelize-typescript"; export class GBAnalyticsPackage implements IGBPackage { + sysPackages: IGBPackage[] = null; + loadPackage(core: IGBCoreService, sequelize: Sequelize): void { } diff --git a/deploy/core.gbapp/index.ts b/deploy/core.gbapp/index.ts index b8795980..c4f4c7c1 100644 --- a/deploy/core.gbapp/index.ts +++ b/deploy/core.gbapp/index.ts @@ -43,7 +43,8 @@ import { Sequelize } from "sequelize-typescript"; import { GuaribasInstance, GuaribasException, GuaribasPackage, GuaribasChannel } from "./models/GBModel"; export class GBCorePackage implements IGBPackage { - + sysPackages: IGBPackage[] = null; + loadPackage(core: IGBCoreService, sequelize: Sequelize): void { core.sequelize.addModels([ GuaribasInstance, diff --git a/deploy/core.gbapp/models/GBModel.ts b/deploy/core.gbapp/models/GBModel.ts index 9f831a68..03b9f1f7 100644 --- a/deploy/core.gbapp/models/GBModel.ts +++ b/deploy/core.gbapp/models/GBModel.ts @@ -91,9 +91,11 @@ export class GuaribasInstance extends Model implements IGBInst @Column webchatKey: string; - @Column whatsappKey: string; + @Column whatsappBotKey: string; + + @Column whatsappServiceKey: string; - @Column spellCheckerKey: string; + @Column spellcheckerKey: string; @Column theme: string; diff --git a/deploy/core.gbapp/services/GBMinService.ts b/deploy/core.gbapp/services/GBMinService.ts index 7e385fa4..968e6bcd 100644 --- a/deploy/core.gbapp/services/GBMinService.ts +++ b/deploy/core.gbapp/services/GBMinService.ts @@ -57,6 +57,7 @@ import { GBDeployer } from './GBDeployer'; import { GBSecurityPackage } from '../../security.gblib'; import { GBAdminPackage } from './../../admin.gbapp/index'; import { GBCustomerSatisfactionPackage } from "../../customer-satisfaction.gbapp"; +import { GBWhatsappPackage } from "../../whatsapp.gblib"; /** Minimal service layer for a bot. */ @@ -155,7 +156,24 @@ export class GBMinService { min.core = _this.core; min.conversationalService = _this.conversationalService; _this.core.loadInstance(min.botId, (data, err) => { + min.instance = data; + + // Call the loadBot event for all packages. + + appPackages.forEach(e => { + e.sysPackages = new Array(); + [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, + GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { + logger.trace(`Loading sys package: ${sysPackage.name}...`); + let p = Object.create(sysPackage.prototype) as IGBPackage; + p.loadBot(min); + e.sysPackages.push(p); + }); + + e.loadBot(min); + }); + }); let connector = new gBuilder.ChatConnector({ @@ -189,9 +207,6 @@ export class GBMinService { storage: inMemoryStorage }); - // Call the loadBot event. - - appPackages.forEach(e => e.loadBot(min)); // Setups handlers. @@ -215,7 +230,7 @@ export class GBMinService { } appPackages.forEach(e => { - e.onNewSession(min, session) + e.onNewSession(min, session); }); next(); @@ -272,13 +287,6 @@ export class GBMinService { } }); - let generalPackages = [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, GBKBPackage, GBCustomerSatisfactionPackage]; - - generalPackages.forEach(e => { - logger.trace(`Loading package: ${e.name}...`); - let p = Object.create(e.prototype) as IGBPackage; - p.loadBot(min); - }); // Specialized load for each min instance. @@ -288,7 +296,7 @@ export class GBMinService { } /** Performs package deployment in all .gbai or default. */ - public deployPackages(core: IGBCoreService, server: any, appPackages: Array, sysPackages: Array) { + public deployPackages(core: IGBCoreService, server: any, appPackages: Array) { return new Promise((resolve, reject) => { try { @@ -356,7 +364,7 @@ export class GBMinService { WaitUntil() .interval(1000) - .times(5) + .times(10) .condition(function (cb) { logger.trace(`Waiting for app package deployment...`); cb(appPackagesProcessed == gbappPackages.length); diff --git a/deploy/customer-satisfaction.gbapp/index.ts b/deploy/customer-satisfaction.gbapp/index.ts index 089ae6d0..3981ca5d 100644 --- a/deploy/customer-satisfaction.gbapp/index.ts +++ b/deploy/customer-satisfaction.gbapp/index.ts @@ -41,7 +41,7 @@ import { Session } from 'botbuilder'; import { Sequelize } from 'sequelize-typescript'; export class GBCustomerSatisfactionPackage implements IGBPackage { - + sysPackages: IGBPackage[] = null; loadPackage(core: IGBCoreService, sequelize: Sequelize): void { core.sequelize.addModels([ GuaribasQuestionAlternate diff --git a/deploy/kb.gbapp/dialogs/AskDialog.ts b/deploy/kb.gbapp/dialogs/AskDialog.ts index d0908867..dad6b4bd 100644 --- a/deploy/kb.gbapp/dialogs/AskDialog.ts +++ b/deploy/kb.gbapp/dialogs/AskDialog.ts @@ -69,7 +69,7 @@ export class AskDialog extends IGBDialog { session.replaceDialog("/menu"); } else { AzureText.getSpelledText( - min.instance.spellCheckerKey, + min.instance.spellcheckerKey, text, (data, err) => { if (data != text) { diff --git a/deploy/kb.gbapp/index.ts b/deploy/kb.gbapp/index.ts index 0fa317a3..39fe4311 100644 --- a/deploy/kb.gbapp/index.ts +++ b/deploy/kb.gbapp/index.ts @@ -44,6 +44,9 @@ import { Sequelize } from 'sequelize-typescript'; import { IGBCoreService } from 'botlib'; export class GBKBPackage implements IGBPackage { + + sysPackages: IGBPackage[] = null; + loadPackage(core: IGBCoreService, sequelize: Sequelize): void { core.sequelize.addModels([ GuaribasAnswer, diff --git a/deploy/security.gblib/index.ts b/deploy/security.gblib/index.ts index 3e4b35d1..78b071b9 100644 --- a/deploy/security.gblib/index.ts +++ b/deploy/security.gblib/index.ts @@ -41,7 +41,7 @@ import { Sequelize } from "sequelize-typescript"; import { GuaribasUser, GuaribasGroup, GuaribasUserGroup } from "./models"; export class GBSecurityPackage implements IGBPackage { - + sysPackages: IGBPackage[] = null; loadPackage(core: IGBCoreService, sequelize: Sequelize): void { core.sequelize.addModels([ GuaribasGroup, diff --git a/deploy/whatsapp.gblib/index.ts b/deploy/whatsapp.gblib/index.ts new file mode 100644 index 00000000..3111a31a --- /dev/null +++ b/deploy/whatsapp.gblib/index.ts @@ -0,0 +1,65 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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. | +| | +\*****************************************************************************/ + +"use strict"; + +const UrlJoin = require("url-join"); + + +import { GBMinInstance, IGBPackage, IGBCoreService } from "botlib"; +import { Session } from 'botbuilder'; +import { Sequelize } from "sequelize-typescript"; +import { WhatsappDirectLine } from "./services/directline"; + + +export class GBWhatsappPackage implements IGBPackage { + sysPackages: IGBPackage[] = null; + channel: WhatsappDirectLine; + + loadPackage(core: IGBCoreService, sequelize: Sequelize): void { + } + + unloadPackage(core: IGBCoreService): void { + + } + + loadBot(min: GBMinInstance): void { + this.channel = new WhatsappDirectLine(min.instance.whatsappBotKey); + } + + unloadBot(min: GBMinInstance): void { + + } + onNewSession(min: GBMinInstance, session: Session): void { + + } +} diff --git a/deploy/whatsapp.gblib/services/directline.ts b/deploy/whatsapp.gblib/services/directline.ts index 915c5b0e..8c35c07e 100644 --- a/deploy/whatsapp.gblib/services/directline.ts +++ b/deploy/whatsapp.gblib/services/directline.ts @@ -39,8 +39,9 @@ const UrlJoin = require("url-join"); const Walk = require("fs-walk"); const logger = require("../../../src/logger"); const Swagger = require('swagger-client'); -const open = require('open'); const rp = require('request-promise'); +import * as request from "request-promise-native"; + import { GBServiceCallback, GBService, IGBInstance } from "botlib"; export class WhatsappDirectLine extends GBService { @@ -55,6 +56,8 @@ export class WhatsappDirectLine extends GBService { this.directLineSecret = directLineSecret; + + // TODO: Migrate to Swagger 3. let directLineClient = rp(this.directLineSpecUrl) .then(function (spec) { return new Swagger({ @@ -64,21 +67,23 @@ export class WhatsappDirectLine extends GBService { }) .then(function (client) { client.clientAuthorizations.add('AuthorizationBotConnector', - new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + this.directLineSecret, 'header')); + new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header')); return client; }) .catch(function (err) { console.error('Error initializing DirectLine client', err); }); + // TODO: Remove *this* issue. + let _this = this; directLineClient.then(function (client) { - client.Conversations.Conversations_StartConversation() // create conversation + client.Conversations.Conversations_StartConversation() .then(function (response) { return response.obj.conversationId; - }) // obtain id + }) .then(function (conversationId) { - this.sendMessagesFromConsole(client, conversationId); // start watching console input for sending new messages to bot - this.pollMessages(client, conversationId); // start polling messages from bot + _this.sendMessagesFromConsole(client, conversationId); + _this.pollMessages(client, conversationId); }) .catch(function (err) { console.error('Error starting conversation', err); @@ -87,6 +92,7 @@ export class WhatsappDirectLine extends GBService { } sendMessagesFromConsole(client, conversationId) { + let _this = this; var stdin = process.openStdin(); process.stdout.write('Command> '); stdin.addListener('data', function (e) { @@ -97,7 +103,6 @@ export class WhatsappDirectLine extends GBService { return process.exit(); } - // send message client.Conversations.Conversations_PostActivity( { conversationId: conversationId, @@ -106,8 +111,8 @@ export class WhatsappDirectLine extends GBService { text: input, type: 'message', from: { - id: this.directLineClientName, - name: this.directLineClientName + id: _this.directLineClientName, + name: _this.directLineClientName } } }).catch(function (err) { @@ -119,8 +124,9 @@ export class WhatsappDirectLine extends GBService { }); } - /** Poll Messages from conversation using DirectLine client */ + /** TBD: Poll Messages from conversation using DirectLine client */ pollMessages(client, conversationId) { + let _this = this; console.log('Starting polling message for conversationId: ' + conversationId); var watermark = null; setInterval(function () { @@ -129,19 +135,22 @@ export class WhatsappDirectLine extends GBService { watermark = response.obj.watermark; // use watermark so subsequent requests skip old messages return response.obj.activities; }) - .then(this.printMessages); + .then(_this.printMessages, _this.directLineClientName); }, this.pollInterval); } - printMessages(activities) { + printMessages(activities, directLineClientName) { + if (activities && activities.length) { // ignore own messages - activities = activities.filter(function (m) { return m.from.id !== this.directLineClientName }); + activities = activities.filter(function (m) { return m.from.id !== directLineClientName }); if (activities.length) { // print other messages - activities.forEach(this.printMessage); + activities.forEach(activity => { + console.log(activity.text); + }); process.stdout.write('Command> '); } @@ -183,4 +192,29 @@ export class WhatsappDirectLine extends GBService { console.log('*' + contentLine(attachment.content.text) + '*'); console.log('*'.repeat(width + 1) + '/'); } + + + async sendToDevice(senderID, msg) { + var options = { + method: 'POST', + url: 'https://www.waboxapp.com/api/send/chat', + qs: + { + token: '', + uid: '55****388**', + to: senderID, + custom_uid: 'GBZAP' + (new Date()).toISOString, + text: msg + }, + headers: + { + 'cache-control': 'no-cache' + } + }; + + const result = await request.get(options); + + } + + } \ No newline at end of file diff --git a/package.json b/package.json index e3a17217..77178316 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "@types/url-join": "^0.8.2", "async": "^1.5.2", "botbuilder": "^3.14.0", - "botlib": "0.0.12", + + "botlib": "0.0.16", "chai": "^4.1.2", "chokidar": "^2.0.2", "csv-parse": "^2.4.0", @@ -48,12 +49,15 @@ "sequelize": "^4.37.6", "sequelize-typescript": "^0.6.3", "sqlite3": "^4.0.0", + "tedious": "^2.1.1", "ts-node": "3.3.0", "typedoc": "^0.10.0", "typescript": "2.7.2", "url-join": "^4.0.0", "wait-until": "0.0.2", - "winston": "^2.4.0" + "winston": "^2.4.0", + + "swagger-client": "^2.1.18" } } diff --git a/src/app.ts b/src/app.ts index 1e47b9ca..41fb4abb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,4 @@ -#! /usr/bin/env node +#! /usr/bin / env node /*****************************************************************************\ | ( )_ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | @@ -42,6 +42,7 @@ import { GBConfigService } from "../deploy/core.gbapp/services/GBConfigService"; import { GBConversationalService } from "../deploy/core.gbapp/services/GBConversationalService"; import { GBMinService } from "../deploy/core.gbapp/services/GBMinService"; import { GBDeployer } from "../deploy/core.gbapp/services/GBDeployer"; +import { GBWhatsappPackage } from './../deploy/whatsapp.gblib/index'; import { GBCoreService } from "../deploy/core.gbapp/services/GBCoreService"; import { GBImporter } from "../deploy/core.gbapp/services/GBImporter"; import { GBAnalyticsPackage } from "../deploy/analytics.gblib"; @@ -52,6 +53,9 @@ import { GBAdminPackage } from '../deploy/admin.gbapp/index'; import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.gbapp"; import { IGBPackage } from 'botlib'; +let appPackages = new Array(); + + /** * General Bots open-core entry point. */ @@ -60,19 +64,17 @@ export class GBServer { /** Program entry-point. */ static run() { - logger.info("Starting General Bots Open Core (Guaribas)..."); - // Creates a basic HTTP server that will serve several URL, one for each // bot instance. This allows the same server to attend multiple Bot on // the Marketplace until GB get serverless. let port = process.env.port || process.env.PORT || 4242; - logger.info(`Starting HTTP BotServer...`); + logger.info(`The Bot Server is in STARTING mode...`); let server = express(); server.listen(port, () => { - logger.info(`General Bot - RUNNING on ${port}...`); + logger.info(`Accepting connections on ${port}...`); logger.info(`Starting instances...`); // Reads basic configuration, initialize minimal services. @@ -90,25 +92,23 @@ export class GBServer { let conversationalService = new GBConversationalService(core); let minService = new GBMinService(core, conversationalService, deployer); - let sysPackages = new Array(); - - [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, - GBKBPackage, GBCustomerSatisfactionPackage].forEach(e => { - logger.trace(`Loading sys package: ${e.name}...`); - let p = Object.create(e.prototype) as IGBPackage; - p.loadPackage(core, core.sequelize); - sysPackages.push(p); - }); + [GBAdminPackage, GBAnalyticsPackage, GBCorePackage, GBSecurityPackage, + GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(e => { + logger.trace(`Loading sys package: ${e.name}...`); + let p = Object.create(e.prototype) as IGBPackage; + p.loadPackage(core, core.sequelize); + + }); (async () => { try { - let appPackages = new Array(); - await minService.deployPackages(core, server, appPackages, sysPackages); - - minService.buildMin(instance => { - logger.info(`Instance loaded: ${instance.botId}...`); - }, server, appPackages); - + await minService.deployPackages(core, server, appPackages); + logger.info(`The Bot Server is in RUNNING mode...`); + + minService.buildMin(instance => { + logger.info(`Instance loaded: ${instance.botId}...`); + }, server, appPackages); + } catch (err) { logger.log(err)