Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add apiai option as an alternative to botkit studio #12

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
# Get one here: https://studio.botkit.ai/
studio_token=''

# Path to APIAI v2 keyfile so your bot can access cloud-hosted content and features from Dialogflow.
# If you do not yet have a keyfile follow the instructions here to generate a service account key: https://dialogflow.com/docs/reference/v2-auth-setup
#apiai_keyfile=''

# Specify your instance of RocketChat
ROCKETCHAT_URL='localhost:3000'

Expand Down Expand Up @@ -41,3 +45,4 @@ RESPOND_TO_EDITED=true
# Enable learning mode, a set of features
# that allow your bot to update itself using Botkit Studio's API
LEARNING_MODE=true

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ The image bellow exemplify this integration.

* Update .env file with your configuration:

NOTE: The bot can be configured to use either Botkit Studio or a Dialogflow APIAI account to fulfill intents depending on which ENV is present.
Dialogflow v2 requires a keyfile for authentication. Instructions for generating the keyfile here: https://dialogflow.com/docs/reference/v2-auth-setup
```
studio_token=<BOTKIT STUDIO TOKEN>
studio_token=<BOTKIT STUDIO TOKEN>
apiai_keyfile=<PATH TO THE DIALOGFLOW V2 KEYFILE FOR AUTHENTICATION>
ROCKETCHAT_URL=<ROCKETCHAT HOST>
ROCKETCHAT_USER=<BOTKIT USER NAME>
ROCKETCHAT_PASSWORD=<BOTKIT USER PASS>
Expand Down
8 changes: 8 additions & 0 deletions bin/botkit_start
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh

set -e

npm install --force
npm install

exec node . "$@"
58 changes: 53 additions & 5 deletions bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
var env = require('node-env-file')
env(__dirname + '/.env')

if (!process.env.ROCKETCHAT_URL || !process.env.ROCKETCHAT_USER || !process.env.ROCKETCHAT_PASS) {
if (!process.env.ROCKETCHAT_URL || !process.env.ROCKETCHAT_USER || !process.env.ROCKETCHAT_PASSWORD) {
usageTip()
}

Expand All @@ -21,6 +21,7 @@ var debug = require('debug')('botkit:main')
var botOptions = {
debug: true,
studio_token: process.env.studio_token,
apiai_keyfile: process.env.apiai_keyfile,
studio_command_uri: process.env.studio_command_uri,
studio_stats_uri: process.env.studio_command_uri,
rocketchat_host: process.env.ROCKETCHAT_URL,
Expand Down Expand Up @@ -49,12 +50,27 @@ require('fs').readdirSync(normalizedPath).forEach(function (file) {
require('./skills/' + file)(controller)
})

if (!process.env.studio_token) {
console.log('~~~~~~~~~~')
console.log('NOTE: Botkit Studio functionality has not been enabled')
console.log('To enable, pass in a studio_token parameter with a token from https://studio.botkit.ai/')
};

if (!process.env.apiai_keyfile) {
console.log('~~~~~~~~~~')
console.log('NOTE: Dialogflow with apiai functionality has not been enabled')
console.log('To enable, pass in a apiai_keyfile parameter with the path to your service account key.')
};


// This captures and evaluates any message sent to the bot as a DM
// or sent to the bot in the form "@bot message" and passes it to
// Botkit Studio to evaluate for trigger words and patterns.
// If a trigger is matched, the conversation will automatically fire!
// You can tie into the execution of the script using the functions
if (process.env.studio_token) {
console.log('-------------')
console.log('Botkit Studio functionality has been enabled')
// TODO: configure the EVENTS here
controller.on(['direct_message', 'live_chat', 'channel', 'mention', 'message'], function (bot, message) {
controller.studio.runTrigger(bot, message.text, message.user, message.channel, message).then(function (convo) {
Expand All @@ -74,10 +90,42 @@ if (process.env.studio_token) {
debug('Botkit Studio: ', err)
})
})
} else {
console.log('~~~~~~~~~~')
console.log('NOTE: Botkit Studio functionality has not been enabled')
console.log('To enable, pass in a studio_token parameter with a token from https://studio.botkit.ai/')
// apiai and dialogflow
} else if (process.env.apiai_keyfile) {
console.log('-------------')
console.log('Dialogflows apiai functionality has been enabled')


const structProtoToJson = require('./structjson').structProtoToJson;
const dialogflowMiddleware = require('botkit-middleware-dialogflow')({
keyFilename: process.env.apiai_keyfile, version: 'v2'
});

controller.middleware.receive.use(dialogflowMiddleware.receive);

controller.middleware.format.use(function (bot, message, platform_message, next) {
console.log("\n*Inside middleware.format.use")
console.log(message)
if (message.fulfillment.text) {
platform_message['text'] = message.fulfillment.text
}

var messages = message.fulfillment.messages
for (var i = 0; i < messages.length; i++) {
if (messages[i].payload && messages[i].payload.fields.attachments) {
platform_message['attachments'] = structProtoToJson(messages[i].payload).attachments
}
}

if (!platform_message.type) {
platform_message.type = 'message';
}
next();
});

controller.on(['direct_message', 'live_chat', 'channel', 'mention', 'message'], function (bot, message) {
bot.reply(message, message, bot);
});
}

function usageTip () {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"body-parser": "^1.15.2",
"botkit-middleware-dialogflow": "^2.0.2",
"botkit": "^0.6.16",
"botkit-rocketchat-connector": "^0.0.17",
"botkit-studio-metrics": "^0.0.4",
Expand Down
100 changes: 100 additions & 0 deletions structjson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Copyright 2017, Google, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @fileoverview Utilities for converting between JSON and goog.protobuf.Struct
* proto.
*/

'use strict';

function jsonToStructProto(json) {
const fields = {};
for (const k in json) {
fields[k] = jsonValueToProto(json[k]);
}

return { fields };
}

const JSON_SIMPLE_TYPE_TO_PROTO_KIND_MAP = {
[typeof 0]: 'numberValue',
[typeof '']: 'stringValue',
[typeof false]: 'boolValue',
};

const JSON_SIMPLE_VALUE_KINDS = new Set(['numberValue', 'stringValue', 'boolValue']);

function jsonValueToProto(value) {
const valueProto = {};

if (value === null) {
valueProto.kind = 'nullValue';
valueProto.nullValue = 'NULL_VALUE';
} else if (value instanceof Array) {
valueProto.kind = 'listValue';
valueProto.listValue = { values: value.map(jsonValueToProto) };
} else if (typeof value === 'object') {
valueProto.kind = 'structValue';
valueProto.structValue = jsonToStructProto(value);
} else if (typeof value in JSON_SIMPLE_TYPE_TO_PROTO_KIND_MAP) {
const kind = JSON_SIMPLE_TYPE_TO_PROTO_KIND_MAP[typeof value];
valueProto.kind = kind;
valueProto[kind] = value;
} else {
console.warn('Unsupported value type ', typeof value);
}
return valueProto;
}

function structProtoToJson(proto) {
if (!proto || !proto.fields) {
return {};
}
const json = {};
for (const k in proto.fields) {
json[k] = valueProtoToJson(proto.fields[k]);
}
return json;
}

function valueProtoToJson(proto) {
if (!proto || !proto.kind) {
return null;
}

if (JSON_SIMPLE_VALUE_KINDS.has(proto.kind)) {
return proto[proto.kind];
} else if (proto.kind === 'nullValue') {
return null;
} else if (proto.kind === 'listValue') {
if (!proto.listValue || !proto.listValue.values) {
console.warn('Invalid JSON list value proto: ', JSON.stringify(proto));
}
return proto.listValue.values.map(valueProtoToJson);
} else if (proto.kind === 'structValue') {
return structProtoToJson(proto.structValue);
} else {
console.warn('Unsupported JSON value proto kind: ', proto.kind);
return null;
}
}

module.exports = {
jsonToStructProto,
structProtoToJson,
};

/* eslint require-jsdoc: off, guard-for-in: off */