Building 3rd party service webhook for Whatsapp.gblib.

Signed-off-by: Rodrigo Rodriguez <me@rodrigorodriguez.com>
This commit is contained in:
Rodrigo Rodriguez 2018-05-12 13:40:34 -03:00
parent ba85db06dd
commit 1d0dc4cf25
5 changed files with 145 additions and 112 deletions

View file

@ -1,9 +1,9 @@
![General Bots Logo](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png)
![General Bot Logo](https://raw.githubusercontent.com/pragmatismo-io/BotServer/master/logo.png)
Welcome to General Bots Community Edition
Welcome to General Bot Community Edition
----------------
General Bots is a package based chat bot server focused in convention
General Bot is a package based chat bot server focused in convention
over configuration and code-less approaches, which brings software packages
and application server concepts to help parallel bot development.
@ -31,7 +31,7 @@ How To
### Run the server locally
1. Install [Node.js](https://www.npmjs.com/get-npm) the current generation General Bots code execution platform;
1. Install [Node.js](https://www.npmjs.com/get-npm) the current generation General Bot code execution platform;
2. Open a **Terminal** on Linux and Mac or a **Command Prompt** window on Windows;npm
3. Type `npm install -g botserver` and press *ENTER*;
4. Type `gbot` to run the server core.
@ -39,7 +39,7 @@ How To
Notes:
* [*nodejs.install* Chocolatey Package](https://chocolatey.org/packages/nodejs.install) is also available.
* The zip source code of general bots is also available for [Download](https://codeload.github.com/pragmatismo-io/BotServer/zip/master);
* The zip source code of General Bot is also available for [Download](https://codeload.github.com/pragmatismo-io/BotServer/zip/master);
### Configure the server to deploy specific directory
@ -56,19 +56,21 @@ Note:
1. [Optional] Install [Chocolatey](https://chocolatey.org/install), a Windows Package Manager;
2. Install [git](`https://git-scm.com/`), a Software Configuration Management (SCM).;
3. Install [Node.js](npmjs.com/get-npm), a [Runtime system](https://en.wikipedia.org/wiki/Runtime_system).
(https://www.npmjs.com/get-npm);
(https://www.npmjs.com/get-npm) (suggested: LTS 8.x.x);
4. Install [Visual Studio Code](https://chocolatey.org/packages/nodejs.install), Brackets or Atom as an editor of your choice;
5. [Fork](https://en.wikipedia.org/wiki/Fork_(software_development)) by visiting https://github.com/pragmatismo-io/BotServer/fork
6. Clone the just forked repository by running `git clone <your-forked-repository-url>/BotServer.git` ;
7. Run `npm install` on Command Prompt or PowerShell on the General Bots source-code folder;
8. Enter './deploy/default.gbui' folder;
9. Run `npm install` folled by `npm run build` (To build default Bot UI);
10. Enter the On the downloaded folder (../..);
11. Run the bot server by `npm start`.
7. Run `npm install -g typescript`;
8. Run `npm install` on Command Prompt or PowerShell on the General Bot source-code folder;
9. Enter './deploy/default.gbui' folder;
10. Run `npm install` folled by `npm run build` (To build default Bot UI);
11. Enter the On the downloaded folder (../..);
12. Compile the bot server by `tsc`.
13. Run the bot server by `npm start`.
Note:
* Whenever you are ready to turn your open-source bot ideas in form of .gbapp (source-code) and artifacts like .gbkb, .gbtheme, .gbot or the .gbai full package read [CONTRIBUTING.md](https://github.com/pragmatismo-io/BotServer/blob/master/CONTRIBUTING.md) about performing Pull Requests (PR) and creating other public custom packages repositories of your own personal or organization General Bots Community Edition powered packages.
* Whenever you are ready to turn your open-source bot ideas in form of .gbapp (source-code) and artifacts like .gbkb, .gbtheme, .gbot or the .gbai full package read [CONTRIBUTING.md](https://github.com/pragmatismo-io/BotServer/blob/master/CONTRIBUTING.md) about performing Pull Requests (PR) and creating other public custom packages repositories of your own personal or organization General Bot Community Edition powered packages.
### Just copy the source code to your machine
@ -81,7 +83,7 @@ The subjects.json file contains all information related to the subject tree and
### Creating a new Theme folder (.gbtheme folder)
A theme is composed of some CSS files and images. That set of files can change
everything in the General Bots UI. Use them extensively before going to change
everything in the General Bot UI. Use them extensively before going to change
the UI application itself (HTML & JS).
Package Types
@ -97,10 +99,10 @@ directory.
The artificial intelligence extensions in form of pluggable apps. Dialogs,
Services and all model related to data. A set of interactions, use cases,
integrations in form of conversationals dialogs.
The .gbapp adds the General Bots base library (botlib) for building Node.js TypeScript Apps packages.
The .gbapp adds the General Bot base library (botlib) for building Node.js TypeScript Apps packages.
Four components builds up a General Bots App:
Four components builds up a General Bot App:
* dialogs
* models
@ -122,7 +124,6 @@ Models builds the foundation of data relationships in form of entities.
Services are a façade for bot back-end logic and other custom processing.
#### Tests
Tests try to automate code execution validation before crashing in production.
@ -152,7 +153,7 @@ Reference
### GeneralBots admin commands
General Bots can be controlled by the same chat window people talk to, so
General Bot can be controlled by the same chat window people talk to, so
here is a list of admin commands related to deploying .gb* files.
| Command | Description |
@ -167,17 +168,17 @@ here is a list of admin commands related to deploying .gb* files.
* Rodrigo Rodriguez (me@rodrigorodriguez.com) - Coding, Docs & Architecture.
* David Lerner (david.lerner@hotmail.com) - UI, UX & Theming
* Eduardo Romeiro (eromeirosp@outlook.com) - Content & UX
* Jorge Ramos (jramos@pobox.com) - Coding, Docs & Architecture.
Powered by Microsoft [BOT Framework](https://dev.botframework.com/) and [Azure](http://www.azure.com).
General Bots Code Name is [Guaribas](https://en.wikipedia.org/wiki/Guaribas), the name of a city in Brasil, state of Piaui.
General Bot Code Name is [Guaribas](https://en.wikipedia.org/wiki/Guaribas), the name of a city in Brasil, state of Piaui.
[Roberto Mangabeira Unger](http://www.robertounger.com/en/): "No one should have to do work that can be done by a machine".
## License & Warranty
General Bots Copyright (c) Pragmatismo.io. All rights reserved.
General Bot 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
@ -193,7 +194,7 @@ 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.
"General Bot" 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.

View file

@ -95,6 +95,8 @@ export class GuaribasInstance extends Model<GuaribasInstance> implements IGBInst
@Column whatsappServiceKey: string;
@Column whatsappServiceNumber: string;
@Column spellcheckerKey: string;
@Column theme: string;

View file

@ -42,6 +42,7 @@ import { WhatsappDirectLine } from "./services/WhatsappDirectLine";
export class GBWhatsappPackage implements IGBPackage {
sysPackages: IGBPackage[] = null;
channel: WhatsappDirectLine;
@ -53,7 +54,8 @@ export class GBWhatsappPackage implements IGBPackage {
}
loadBot(min: GBMinInstance): void {
this.channel = new WhatsappDirectLine(min.instance.whatsappBotKey);
this.channel = new WhatsappDirectLine(min.botId, min.instance.whatsappBotKey, min.instance.whatsappServiceKey,
min.instance.whatsappServiceNumber);
}
unloadBot(min: GBMinInstance): void {

View file

@ -47,18 +47,23 @@ import { GBServiceCallback, GBService, IGBInstance } from "botlib";
export class WhatsappDirectLine extends GBService {
pollInterval = 1000;
directLineSecret = '';
directLineClientName = 'DirectLineClient';
directLineSpecUrl = 'https://docs.botframework.com/en-us/restapi/directline3/swagger.json';
directLineClient: any;
whatsappServiceKey: string;
whatsappServiceNumber: string;
botId: string;
constructor(botId, directLineSecret, whatsappServiceKey, whatsappServiceNumber) {
constructor(directLineSecret) {
super();
this.directLineSecret = directLineSecret;
this.botId = botId;
this.whatsappServiceKey = whatsappServiceKey;
this.whatsappServiceNumber = whatsappServiceNumber;
// TODO: Migrate to Swagger 3.
let directLineClient = rp(this.directLineSpecUrl)
this.directLineClient = rp(this.directLineSpecUrl)
.then(function (spec) {
return new Swagger({
spec: JSON.parse(spec.trim()),
@ -67,147 +72,173 @@ export class WhatsappDirectLine extends GBService {
})
.then(function (client) {
client.clientAuthorizations.add('AuthorizationBotConnector',
new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + directLineSecret, 'header'));
new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' +
directLineSecret, 'header'));
return client;
})
.catch(function (err) {
console.error('Error initializing DirectLine client', err);
logger.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);
});
});
}
received (req,res){
logger.info(`Whatsapp called. Event: ${req.body.event}, muid: ${req.body.muid}, cuid: ${req.body.cuid}`);
res.end();
}
received(req, res) {
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();
}
logger.info(`GBWhatsapp: Hook called. Event: ${req.body.event},
muid: ${req.body.muid}, contact: ${req.body.contact.name},
(${req.body.contact.uid}, ${req.body.contact.type})`);
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);
let conversationId = null; // req.body.cuid;
let text = req.body.message.body.text;
let from = req.body.contact.uid;
let fromName = req.body.contact.name;
this.directLineClient.then((client) => {
if (conversationId == null) {
logger.info(`GBWhatsapp: Starting new conversation on Bot.`);
client.Conversations.Conversations_StartConversation()
.then((response) => {
return response.obj.conversationId;
})
.then((conversationId) => {
this.inputMessage(client, conversationId, text,
from, fromName);
this.pollMessages(client, conversationId,from, fromName);
})
.catch((err) => {
console.error('Error starting conversation', err);
});
process.stdout.write('Command> ');
} else {
this.inputMessage(client, conversationId, text,
from, fromName);
this.pollMessages(client, conversationId, from, fromName);
}
res.end();
});
}
/** TBD: Poll Messages from conversation using DirectLine client */
pollMessages(client, conversationId) {
let _this = this;
console.log('Starting polling message for conversationId: ' + conversationId);
inputMessage(client, conversationId, text, from, fromName) {
client.Conversations.Conversations_PostActivity(
{
conversationId: conversationId,
activity: {
textFormat: 'plain',
text: text,
type: 'message',
from: {
id: from,
name: fromName
},
replyToId: from
}
}).catch(function (err) {
logger.error(`GBWhatsapp: Error receiving message: ${err}.`);
});
}
pollMessages(client, conversationId,from, fromName){
logger.info(`GBWhatsapp: 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
setInterval(() => {
client.Conversations.Conversations_GetActivities({
conversationId:
conversationId, watermark: watermark
})
.then((response) => {
watermark = response.obj.watermark;
return response.obj.activities;
})
.then(_this.printMessages, _this.directLineClientName);
.then((activities) => {
this.printMessages(activities, from, fromName);
});
}, this.pollInterval);
}
printMessages(activities, directLineClientName) {
printMessages(activities,from, fromName) {
if (activities && activities.length) {
// ignore own messages
activities = activities.filter(function (m) { return m.from.id !== directLineClientName });
// Ignore own messages.
activities = activities.filter((m) => { return m.from.id === this.botId });
if (activities.length) {
// print other messages
activities.forEach(activity => {
console.log(activity.text);
});
// Print other messages.
process.stdout.write('Command> ');
activities.forEach(activity => {
this.printMessage(activity, from, fromName);
});
}
}
}
printMessage(activity) {
printMessage(activity, from, fromName) {
let output: string;
if (activity.text) {
console.log(activity.text);
logger.info(`GBWhatsapp: MSG: ${activity.text}`);
output = activity.text;
}
if (activity.attachments) {
activity.attachments.forEach(function (attachment) {
switch (attachment.contentType) {
case "application/vnd.microsoft.card.hero":
this.renderHeroCard(attachment);
output += this.renderHeroCard(attachment);
break;
case "image/png":
console.log('Opening the requested image ' + attachment.contentUrl);
open(attachment.contentUrl);
logger.info('Opening the requested image ' + attachment.contentUrl);
output += `\n${attachment.contentUrl}`;
break;
}
});
}
this.sendToDevice(from, fromName, output);
}
renderHeroCard(attachment) {
var width = 70;
var contentLine = function (content) {
let output: string;
let width = 70;
let 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) + '/');
output += '/' + '*'.repeat(width + 1);
output += '*' + contentLine(attachment.content.title) + '*';
output += '*' + ' '.repeat(width) + '*';
output += '*' + contentLine(attachment.content.text) + '*';
output += '*'.repeat(width + 1) + '/';
}
async sendToDevice(senderID, msg) {
async sendToDevice(to, toName, msg) {
var options = {
method: 'POST',
url: 'https://www.waboxapp.com/api/send/chat',
qs:
{
token: '',
uid: '55****388**',
to: senderID,
token: this.whatsappServiceKey,
uid: this.whatsappServiceNumber,
to: to,
custom_uid: 'GBZAP' + (new Date()).toISOString,
text: msg
},
@ -218,8 +249,5 @@ export class WhatsappDirectLine extends GBService {
};
const result = await request.get(options);
}
}

View file

@ -32,7 +32,7 @@
"async": "^1.5.2",
"body-parser": "^1.18.2",
"botbuilder": "^3.14.0",
"botlib": "0.0.16",
"botlib": "0.0.18",
"chai": "^4.1.2",
"chokidar": "^2.0.2",
"csv-parse": "^2.4.0",