Skip to content

Commit

Permalink
init from bot_hosting-sample
Browse files Browse the repository at this point in the history
  • Loading branch information
ObjectIsAdvantag committed Sep 19, 2017
1 parent b5fe1c3 commit a2aaf2e
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 101 deletions.
6 changes: 5 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,8 @@ HEALTHCHECK_ROUTE="/"
# Metadata route, defaults to "/botcommons"
BOTCOMMONS_ROUTE="/botcommons"


# Uncomment to use Redis storage
# for local dev
#REDIS_URL="redis://127.0.0.1:6379"
# example for Heroku Redis
#REDIS_URL="redis://h:[email protected]:60109"
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"envFile": "${workspaceRoot}/.env",
"env": {
"SPARK_TOKEN": "PLACE_YOUR_BOT_TOKEN_HERE_FOR_DEBUG_PURPOSE",
"PUBLIC_URL": "https://718670a3.ngrok.io"
"PUBLIC_URL": "https://718670a3.ngrok.io",
"REDIS_URL": "redis://127.0.0.1:6379"
}
}
]
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

This template regroups a set of best practices:

- configuration: pass settings either through environment variables on the command line, or hard-coded values in the `.env` file. Note that command line variables are priorized over the `.env` file if present in both places.
- plugable architecture: as Botkit launches, several directories are loaded to customize your bot, in the order below:
1. configurations: add complex configuration settings that get activated from env variables
2. extensions: add extra utility function to the bot object
3. plugins: add extra routes/middlewares to your bot
4. skills: organize your bot behaviours by placing 'commands', 'conversations' and 'events' in the [skills directory](skills/README.md).

- skills: organize your bot behaviours by placing 'commands', 'conversations' and 'events' in the [skills directory](skills/README.md).
- configuration: pass settings either through environment variables on the command line, or hard-coded values in the `.env` file. Note that command line variables are priorized over the `.env` file if present in both places.

- user experience: the template comes ready-to-use skills: a 'welcome' invite, as well as 'help' and 'fallback' commands.

Expand All @@ -14,7 +18,7 @@ This template regroups a set of best practices:

- mentions: the appendMention utility function helps Spark users remind to mention the bot in Group spaces.

- popular cloud providers: the bot self-configures when run on Glitch and Heroku (if )
- popular cloud providers: the bot self-configures when run on Glitch and Heroku (if dyno metadata have been enabled via the Heroku CLI)


## Quick start on Glitch
Expand Down
177 changes: 112 additions & 65 deletions bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
// Licensed under the MIT License
//


var debug = require('debug')('starterkit');


//
// BotKit configuration
//

// Load environment variables from project .env file
require('node-env-file')(__dirname + '/.env');

// Fail fast
if (!process.env.SPARK_TOKEN) {
console.log("Could not start as bots require a Cisco Spark API access token.");
console.log("Please add env variable SPARK_TOKEN on the command line or to the .env file");
Expand Down Expand Up @@ -49,90 +54,132 @@ if (!public_url) {
var Botkit = require('botkit');

var env = process.env.NODE_ENV || "development";
var controller = Botkit.sparkbot({
log: true,
public_address: public_url,

var configuration = {
public_address: process.env.PUBLIC_URL,
ciscospark_access_token: process.env.SPARK_TOKEN,
secret: process.env.SECRET, // this is a RECOMMENDED security setting that checks of incoming payloads originate from Cisco Spark
secret: process.env.SECRET, // this is a RECOMMENDED security setting that checks if incoming payloads originate from Cisco Spark
webhook_name: process.env.WEBHOOK_NAME || ('built with BotKit (' + env + ')')
});

var bot = controller.spawn({
});


//
// Launch bot
//
}

var port = process.env.PORT || 3000;
controller.setupWebserver(port, function (err, webserver) {
controller.createWebhookEndpoints(webserver, bot, function () {
console.log("Cisco Spark: Webhooks set up!");
// Load extra configuration modules
try {
var configurationPath = require("path").join(__dirname, "configurations");
require("fs").readdirSync(configurationPath).forEach(function (file) {
try {
if (file.endsWith(".js")) {
require("./configurations/" + file)(configuration);
console.log("loaded configuration:" + file);
}
}
catch (err) {
console.log("error, configuration not loaded: " + file);
}
});
}
catch (err) {
if (err.code == "ENOENT") {
debug("configurations directory not present, continuing....");
}
else {
// fail fast
throw err;
}
}

// installing Healthcheck
var healthcheck = {
"up-since": new Date(Date.now()).toGMTString(),
"hostname": require('os').hostname() + ":" + port,
"version": "v" + require("./package.json").version,
"bot": "unknown", // loaded asynchronously
"botkit": "v" + bot.botkit.version()
};
webserver.get(process.env.HEALTHCHECK_ROUTE, function (req, res) {

// As the identity is load asynchronously from Cisco Spark token, we need to check until it's fetched
if (healthcheck.bot == "unknown") {
var identity = bot.botkit.identity;
if (bot.botkit.identity) {
healthcheck.bot = bot.botkit.identity.emails[0];
var controller = require('botkit').sparkbot(configuration);

var sparkbot = controller.spawn({}, function (bot) {

// Load bot extensions: append_mention, botcommons metadata
try {
var extensionsPath = require("path").join(__dirname, "extensions");
require("fs").readdirSync(extensionsPath).forEach(function (file) {
try {
if (file.endsWith(".js")) {
require("./extensions/" + file)(bot);
debug("extension loaded: " + file);
}
}
catch (err) {
debug("error, could not load extension: " + file);
}
});
}
catch (err) {
if (err.code == "ENOENT") {
debug("extensions directory not present, continuing....");
}
else {
// fail fast
throw err;
}
}

res.json(healthcheck);
});
console.log("Cisco Spark: healthcheck available at: " + process.env.HEALTHCHECK_ROUTE);
});


//
// Load skills
// Launch bot
//

var normalizedPath = require("path").join(__dirname, "skills");
require("fs").readdirSync(normalizedPath).forEach(function (file) {
// Start Bot API
controller.setupWebserver(process.env.PORT || 3000, function (err, webserver) {
if (err) {
console.log("could not start Web server, existing... err: " + err.message);
throw err;
}

controller.createWebhookEndpoints(webserver, sparkbot, function (err, success) {
debug("Webhook successfully setup");
});

// Load extra plugins: middlewares, healthchecks...
try {
require("./skills/" + file)(controller, bot);
console.log("Cisco Spark: loaded skill: " + file);
var pluginsPath = require("path").join(__dirname, "plugins");
require("fs").readdirSync(pluginsPath).forEach(function (file) {
try {
if (file.endsWith(".js")) {
require("./plugins/" + file)(controller, sparkbot);
debug("plugin loaded: " + file);
}
}
catch (err) {
debug("error, could not load plugin: " + file);
}
});
}
catch (err) {
if (err.code == "MODULE_NOT_FOUND") {
if (file != "utils") {
console.log("Cisco Spark: could not load skill: " + file);
}
if (err.code == "ENOENT") {
debug("plugins directory not present, continuing....");
}
else {
// fail fast
throw err;
}
}
});


//
// Cisco Spark Utilities
//

// Utility to add mentions if Bot is in a 'Group' space
bot.appendMention = function (message, command) {

// if the message is a raw message (from a post message callback such as bot.say())
if (message.roomType && (message.roomType == "group")) {
var botName = bot.botkit.identity.displayName;
return "`@" + botName + " " + command + "`";
// Load skills
try {
var skillsPath = require("path").join(__dirname, "skills");
require("fs").readdirSync(skillsPath).forEach(function (file) {
try {
if (file.endsWith(".js")) {
require("./skills/" + file)(controller);
debug("skill loaded: " + file);
}
}
catch (err) {
debug("error, could not load skill: " + file);
}
});
}

// if the message is a Botkit message
if (message.raw_message && (message.raw_message.data.roomType == "group")) {
var botName = bot.botkit.identity.displayName;
return "`@" + botName + " " + command + "`";
catch (err) {
if (err.code == "ENOENT") {
debug("skills directory not present, aborting....");
}

// fail fast
throw err;
}
});

return "`" + command + "`";
}
3 changes: 3 additions & 0 deletions configurations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This place is your chance to customize your bot configuration.

Place here extra Botkit components you want to add, these will be loaded in alphabetical order.
46 changes: 46 additions & 0 deletions configurations/redis-storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Copyright (c) 2017 Cisco Systems
// Licensed under the MIT License
//

/**
* Adds a persistent storage to Redis to BotKit configuration
*
* Setup instructions:
* - add a REDIS_URL env variable pointing to the redis instance
* - add a project dependency to the 'botkit-storage-redis' module, aka "npm install botkit-storage-redis"
* - [optional] customize the configuration below
*/
module.exports = function (configuration) {

if (process.env.REDIS_URL) {

// Initialize Redis storage
var redisConfig = {
// for local dev: redis://127.0.0.1:6379
// if on heroku : redis://h:PASSWORD@ec2-54-86-77-126.compute-1.amazonaws.com:60109
url: process.env.REDIS_URL

// uncomment to add extra global key spaces to store data, example:
//, methods: ['activities']

// uncomment to override the Redis namespace prefix, Defaults to 'botkit:store', example:
//, namespace: 'cisco:devnet'
};

// Create Redis storage for BotKit
try {
var redisStorage = require('botkit-storage-redis')(redisConfig);

configuration.storage = redisStorage;
console.log("Redis storage successfully initialized");

// Note that we did not ping'ed Redis yet
// then a 'ECONNREFUSED' error will be thrown if the Redis can be ping'ed later in the initialization process
// which is fine in a "Fail Fast" strategy
}
catch (err) {
console.log("Could not initialise Redis storage, check the provided Redis URL, err: " + err.message);
}
}
}
11 changes: 11 additions & 0 deletions configurations/stats_optout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// Copyright (c) 2017 Cisco Systems
// Licensed under the MIT License
//

module.exports = function (configuration) {

// Botkit sends stats to the cloud by default
// Uncomment to stop sending
//configuration["stats_optout"] = true;
}
7 changes: 7 additions & 0 deletions extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Place in this directory the extensions to be loaded into the bot spawned from BotKit controller.

This is your opportunity to enrich the interactions with Cisco Spark.
by adding extra properties and functions to the `bot` object passed to your skills.

Examples:
- appendMention utility: append the bot name to a command if the current room is a 'Group' space
29 changes: 29 additions & 0 deletions extensions/append_mention.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Copyright (c) 2017 Cisco Systems
// Licensed under the MIT License
//

/*
* Utility to add mentions if Bot is in a 'Group' space
*
*/
module.exports = function (bot) {

// Utility to add mentions if Bot is in a 'Group' space
bot.appendMention = function (message, command) {

// if the message is a raw message (from a post message callback such as bot.say())
if (message.roomType && (message.roomType == "group")) {
var botName = bot.botkit.identity.displayName;
return "`@" + botName + " " + command + "`";
}

// if the message is a Botkit message
if (message.raw_message && (message.raw_message.data.roomType == "group")) {
var botName = bot.botkit.identity.displayName;
return "`@" + botName + " " + command + "`";
}

return "`" + command + "`";
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"description": "Botkit template for Cisco Spark",
"version": "0.6.0",
"version": "0.6.0-plugin1",
"main": "bot.js",
"scripts": {
"start": "node bot.js"
Expand All @@ -14,6 +14,7 @@
"license": "MIT",
"dependencies": {
"botkit": "0.6.0",
"botkit-storage-redis": "1.1.0",
"node-env-file": "0.1.8"
},
"repository": {
Expand Down
Loading

0 comments on commit a2aaf2e

Please sign in to comment.