diff --git a/VERSION.md b/VERSION.md index 0762b842..662e6d6d 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1,3 +1,10 @@ +## Version 0.0.19 + +- NEW: Whatsapp directline client started. +- NEW: Console directline client. +- NEW: Now each .gbapp has it own set of syspackages loaded. +- NEW: Added support for Whatsapp external service key on bot instance model. + ## Version 0.0.18 - FIX: .gbapp files now correctly loaded before other package types so custom models can be used to sync DB. - NEW: Removed Boot Package feature. Now every .gbot found on deploy folders are deployed on startup. diff --git a/deploy/console.gblib/index.ts b/deploy/console.gblib/index.ts new file mode 100644 index 00000000..871b6663 --- /dev/null +++ b/deploy/console.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 { ConsoleDirectLine } from "./services/ConsoleDirectLine"; + + +export class GBConsolePackage implements IGBPackage { + sysPackages: IGBPackage[] = null; + channel: ConsoleDirectLine; + + loadPackage(core: IGBCoreService, sequelize: Sequelize): void { + } + + unloadPackage(core: IGBCoreService): void { + + } + + loadBot(min: GBMinInstance): void { + this.channel = new ConsoleDirectLine(min.instance.webchatKey); + } + + unloadBot(min: GBMinInstance): void { + + } + onNewSession(min: GBMinInstance, session: Session): void { + + } +} diff --git a/deploy/console.gblib/services/ConsoleDirectLine.ts b/deploy/console.gblib/services/ConsoleDirectLine.ts new file mode 100644 index 00000000..fc503946 --- /dev/null +++ b/deploy/console.gblib/services/ConsoleDirectLine.ts @@ -0,0 +1,198 @@ +/*****************************************************************************\ +| ( )_ _ | +| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | +| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' _ `\ /'_`\ | +| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| ( ) |( (_) ) | +| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | +| | | ( )_) | | +| (_) \___/' | +| | +| 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. | +| | +\*****************************************************************************/ + +const Path = require("path"); +const Fs = require("fs"); +const _ = require("lodash"); +const Parse = require("csv-parse"); +const Async = require("async"); +const UrlJoin = require("url-join"); +const Walk = require("fs-walk"); +const logger = require("../../../src/logger"); +const Swagger = require('swagger-client'); +const rp = require('request-promise'); +import * as request from "request-promise-native"; + +import { GBServiceCallback, GBService, IGBInstance } from "botlib"; + +export class ConsoleDirectLine extends GBService { + + pollInterval = 1000; + directLineSecret = ''; + directLineClientName = 'DirectLineClient'; + directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json'; + + constructor(directLineSecret) { + super(); + + this.directLineSecret = directLineSecret; + + + // TODO: Migrate to Swagger 3. + let directLineClient = rp(this.directLineSpecUrl) + .then(function (spec) { + return new Swagger({ + spec: JSON.parse(spec.trim()), + usePromise: true + }); + }) + .then(function (client) { + client.clientAuthorizations.add('AuthorizationBotConnector', + 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() + .then(function (response) { + return response.obj.conversationId; + }) + .then(function (conversationId) { + _this.sendMessagesFromConsole(client, conversationId); + _this.pollMessages(client, conversationId); + }) + .catch(function (err) { + console.error('Error starting conversation', err); + }); + }); + } + + sendMessagesFromConsole(client, conversationId) { + let _this = this; + var stdin = process.openStdin(); + process.stdout.write('Command> '); + stdin.addListener('data', function (e) { + var input = e.toString().trim(); + if (input) { + // exit + if (input.toLowerCase() === 'exit') { + return process.exit(); + } + + client.Conversations.Conversations_PostActivity( + { + conversationId: conversationId, + activity: { + textFormat: 'plain', + text: input, + type: 'message', + from: { + id: _this.directLineClientName, + name: _this.directLineClientName + } + } + }).catch(function (err) { + console.error('Error sending message:', err); + }); + + process.stdout.write('Command> '); + } + }); + } + + /** 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 () { + client.Conversations.Conversations_GetActivities({ conversationId: conversationId, watermark: watermark }) + .then(function (response) { + watermark = response.obj.watermark; // use watermark so subsequent requests skip old messages + return response.obj.activities; + }) + .then(_this.printMessages, _this.directLineClientName); + }, this.pollInterval); + } + + printMessages(activities, directLineClientName) { + + if (activities && activities.length) { + // ignore own messages + activities = activities.filter(function (m) { return m.from.id !== directLineClientName }); + + if (activities.length) { + + // print other messages + activities.forEach(activity => { + console.log(activity.text); + }); + + process.stdout.write('Command> '); + } + } + } + + printMessage(activity) { + if (activity.text) { + console.log(activity.text); + } + + if (activity.attachments) { + activity.attachments.forEach(function (attachment) { + switch (attachment.contentType) { + case "application/vnd.microsoft.card.hero": + this.renderHeroCard(attachment); + break; + + case "image/png": + console.log('Opening the requested image ' + attachment.contentUrl); + open(attachment.contentUrl); + break; + } + }); + } + } + + renderHeroCard(attachment) { + var width = 70; + var contentLine = function (content) { + return ' '.repeat((width - content.length) / 2) + + content + + ' '.repeat((width - content.length) / 2); + } + + console.log('/' + '*'.repeat(width + 1)); + console.log('*' + contentLine(attachment.content.title) + '*'); + console.log('*' + ' '.repeat(width) + '*'); + console.log('*' + contentLine(attachment.content.text) + '*'); + console.log('*'.repeat(width + 1) + '/'); + } + + + +} \ No newline at end of file diff --git a/deploy/core.gbapp/services/GBConfigService.ts b/deploy/core.gbapp/services/GBConfigService.ts index d972b4a9..f1f16e7e 100644 --- a/deploy/core.gbapp/services/GBConfigService.ts +++ b/deploy/core.gbapp/services/GBConfigService.ts @@ -70,10 +70,6 @@ export class GBConfigService { value = undefined; break; - case "DEFAULT_AI": - value = undefined; - break; - case "DATABASE_SYNC": value = "false"; break; diff --git a/deploy/core.gbapp/services/GBMinService.ts b/deploy/core.gbapp/services/GBMinService.ts index 968e6bcd..cc1d987c 100644 --- a/deploy/core.gbapp/services/GBMinService.ts +++ b/deploy/core.gbapp/services/GBMinService.ts @@ -115,7 +115,8 @@ export class GBMinService { Authorization: `Bearer ${instance.webchatKey}` } }; - request(options).then((response: string) => { + request(options).then((response: + string) => { // Serves the bot information object via http so clients can get // instance information stored on server. @@ -167,10 +168,17 @@ export class GBMinService { GBKBPackage, GBCustomerSatisfactionPackage, GBWhatsappPackage].forEach(sysPackage => { logger.trace(`Loading sys package: ${sysPackage.name}...`); let p = Object.create(sysPackage.prototype) as IGBPackage; - p.loadBot(min); + p.loadBot(min); e.sysPackages.push(p); - }); - + + if (sysPackage.name === "GBWhatsappPackage") { + server.post("/instances/:botId/whatsapp", (req, res) => { + p["channel"].received(req, res); + }); + } + + }); + e.loadBot(min); }); diff --git a/deploy/whatsapp.gblib/index.ts b/deploy/whatsapp.gblib/index.ts index 3111a31a..a96e1187 100644 --- a/deploy/whatsapp.gblib/index.ts +++ b/deploy/whatsapp.gblib/index.ts @@ -38,7 +38,7 @@ 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"; +import { WhatsappDirectLine } from "./services/WhatsappDirectLine"; export class GBWhatsappPackage implements IGBPackage { diff --git a/deploy/whatsapp.gblib/services/directline.ts b/deploy/whatsapp.gblib/services/WhatsappDirectLine.ts similarity index 98% rename from deploy/whatsapp.gblib/services/directline.ts rename to deploy/whatsapp.gblib/services/WhatsappDirectLine.ts index 8c35c07e..733bc77f 100644 --- a/deploy/whatsapp.gblib/services/directline.ts +++ b/deploy/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -56,7 +56,7 @@ export class WhatsappDirectLine extends GBService { this.directLineSecret = directLineSecret; - + // TODO: Migrate to Swagger 3. let directLineClient = rp(this.directLineSpecUrl) .then(function (spec) { @@ -91,6 +91,11 @@ export class WhatsappDirectLine extends GBService { }); } + received (req,res){ + logger.info(`Whatsapp called. Event: ${req.body.event}, muid: ${req.body.muid}, cuid: ${req.body.cuid}`); + res.end(); + } + sendMessagesFromConsole(client, conversationId) { let _this = this; var stdin = process.openStdin(); diff --git a/package.json b/package.json index 77178316..48aa256e 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "@types/mocha": "2.2.43", "@types/url-join": "^0.8.2", "async": "^1.5.2", + "body-parser": "^1.18.2", "botbuilder": "^3.14.0", - "botlib": "0.0.16", "chai": "^4.1.2", "chokidar": "^2.0.2", @@ -49,15 +49,13 @@ "sequelize": "^4.37.6", "sequelize-typescript": "^0.6.3", "sqlite3": "^4.0.0", - + "swagger-client": "^2.1.18", "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", - - "swagger-client": "^2.1.18" + "winston": "^2.4.0" } } diff --git a/src/app.ts b/src/app.ts index 41fb4abb..34c3ef63 100644 --- a/src/app.ts +++ b/src/app.ts @@ -36,6 +36,7 @@ const UrlJoin = require("url-join"); const logger = require("./logger"); const express = require("express"); +const bodyParser = require("body-parser"); import { UniversalBot } from "botbuilder"; import { Sequelize } from "sequelize-typescript"; import { GBConfigService } from "../deploy/core.gbapp/services/GBConfigService"; @@ -54,7 +55,7 @@ import { GBCustomerSatisfactionPackage } from "../deploy/customer-satisfaction.g import { IGBPackage } from 'botlib'; let appPackages = new Array(); - + /** * General Bots open-core entry point. @@ -72,6 +73,11 @@ export class GBServer { logger.info(`The Bot Server is in STARTING mode...`); let server = express(); + server.use(bodyParser.json()); // to support JSON-encoded bodies + server.use(bodyParser.urlencoded({ // to support URL-encoded bodies + extended: true + })); + server.listen(port, () => { logger.info(`Accepting connections on ${port}...`); @@ -97,7 +103,7 @@ export class GBServer { logger.trace(`Loading sys package: ${e.name}...`); let p = Object.create(e.prototype) as IGBPackage; p.loadPackage(core, core.sequelize); - + }); (async () => {