Skip to content

Commit a2aaf2e

Browse files
init from bot_hosting-sample
1 parent b5fe1c3 commit a2aaf2e

14 files changed

Lines changed: 347 additions & 101 deletions

File tree

.env

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,8 @@ HEALTHCHECK_ROUTE="/"
5656
# Metadata route, defaults to "/botcommons"
5757
BOTCOMMONS_ROUTE="/botcommons"
5858

59-
59+
# Uncomment to use Redis storage
60+
# for local dev
61+
#REDIS_URL="redis://127.0.0.1:6379"
62+
# example for Heroku Redis
63+
#REDIS_URL="redis://h:PASSWORD@ec2-54-86-77-126.compute-1.amazonaws.com:60109"

.vscode/launch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"envFile": "${workspaceRoot}/.env",
1010
"env": {
1111
"SPARK_TOKEN": "PLACE_YOUR_BOT_TOKEN_HERE_FOR_DEBUG_PURPOSE",
12-
"PUBLIC_URL": "https://718670a3.ngrok.io"
12+
"PUBLIC_URL": "https://718670a3.ngrok.io",
13+
"REDIS_URL": "redis://127.0.0.1:6379"
1314
}
1415
}
1516
]

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
This template regroups a set of best practices:
44

5-
- 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.
5+
- plugable architecture: as Botkit launches, several directories are loaded to customize your bot, in the order below:
6+
1. configurations: add complex configuration settings that get activated from env variables
7+
2. extensions: add extra utility function to the bot object
8+
3. plugins: add extra routes/middlewares to your bot
9+
4. skills: organize your bot behaviours by placing 'commands', 'conversations' and 'events' in the [skills directory](skills/README.md).
610

7-
- skills: organize your bot behaviours by placing 'commands', 'conversations' and 'events' in the [skills directory](skills/README.md).
11+
- 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.
812

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

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

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

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

1923

2024
## Quick start on Glitch

bot.js

Lines changed: 112 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@
33
// Licensed under the MIT License
44
//
55

6+
7+
var debug = require('debug')('starterkit');
8+
9+
610
//
711
// BotKit configuration
812
//
913

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

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

5156
var env = process.env.NODE_ENV || "development";
52-
var controller = Botkit.sparkbot({
53-
log: true,
54-
public_address: public_url,
57+
58+
var configuration = {
59+
public_address: process.env.PUBLIC_URL,
5560
ciscospark_access_token: process.env.SPARK_TOKEN,
56-
secret: process.env.SECRET, // this is a RECOMMENDED security setting that checks of incoming payloads originate from Cisco Spark
61+
secret: process.env.SECRET, // this is a RECOMMENDED security setting that checks if incoming payloads originate from Cisco Spark
5762
webhook_name: process.env.WEBHOOK_NAME || ('built with BotKit (' + env + ')')
58-
});
59-
60-
var bot = controller.spawn({
61-
});
62-
63-
64-
//
65-
// Launch bot
66-
//
63+
}
6764

68-
var port = process.env.PORT || 3000;
69-
controller.setupWebserver(port, function (err, webserver) {
70-
controller.createWebhookEndpoints(webserver, bot, function () {
71-
console.log("Cisco Spark: Webhooks set up!");
65+
// Load extra configuration modules
66+
try {
67+
var configurationPath = require("path").join(__dirname, "configurations");
68+
require("fs").readdirSync(configurationPath).forEach(function (file) {
69+
try {
70+
if (file.endsWith(".js")) {
71+
require("./configurations/" + file)(configuration);
72+
console.log("loaded configuration:" + file);
73+
}
74+
}
75+
catch (err) {
76+
console.log("error, configuration not loaded: " + file);
77+
}
7278
});
79+
}
80+
catch (err) {
81+
if (err.code == "ENOENT") {
82+
debug("configurations directory not present, continuing....");
83+
}
84+
else {
85+
// fail fast
86+
throw err;
87+
}
88+
}
7389

74-
// installing Healthcheck
75-
var healthcheck = {
76-
"up-since": new Date(Date.now()).toGMTString(),
77-
"hostname": require('os').hostname() + ":" + port,
78-
"version": "v" + require("./package.json").version,
79-
"bot": "unknown", // loaded asynchronously
80-
"botkit": "v" + bot.botkit.version()
81-
};
82-
webserver.get(process.env.HEALTHCHECK_ROUTE, function (req, res) {
83-
84-
// As the identity is load asynchronously from Cisco Spark token, we need to check until it's fetched
85-
if (healthcheck.bot == "unknown") {
86-
var identity = bot.botkit.identity;
87-
if (bot.botkit.identity) {
88-
healthcheck.bot = bot.botkit.identity.emails[0];
90+
var controller = require('botkit').sparkbot(configuration);
91+
92+
var sparkbot = controller.spawn({}, function (bot) {
93+
94+
// Load bot extensions: append_mention, botcommons metadata
95+
try {
96+
var extensionsPath = require("path").join(__dirname, "extensions");
97+
require("fs").readdirSync(extensionsPath).forEach(function (file) {
98+
try {
99+
if (file.endsWith(".js")) {
100+
require("./extensions/" + file)(bot);
101+
debug("extension loaded: " + file);
102+
}
103+
}
104+
catch (err) {
105+
debug("error, could not load extension: " + file);
106+
}
107+
});
108+
}
109+
catch (err) {
110+
if (err.code == "ENOENT") {
111+
debug("extensions directory not present, continuing....");
112+
}
113+
else {
114+
// fail fast
115+
throw err;
89116
}
90117
}
91-
92-
res.json(healthcheck);
93118
});
94-
console.log("Cisco Spark: healthcheck available at: " + process.env.HEALTHCHECK_ROUTE);
95-
});
96119

97120

98121
//
99-
// Load skills
122+
// Launch bot
100123
//
101124

102-
var normalizedPath = require("path").join(__dirname, "skills");
103-
require("fs").readdirSync(normalizedPath).forEach(function (file) {
125+
// Start Bot API
126+
controller.setupWebserver(process.env.PORT || 3000, function (err, webserver) {
127+
if (err) {
128+
console.log("could not start Web server, existing... err: " + err.message);
129+
throw err;
130+
}
131+
132+
controller.createWebhookEndpoints(webserver, sparkbot, function (err, success) {
133+
debug("Webhook successfully setup");
134+
});
135+
136+
// Load extra plugins: middlewares, healthchecks...
104137
try {
105-
require("./skills/" + file)(controller, bot);
106-
console.log("Cisco Spark: loaded skill: " + file);
138+
var pluginsPath = require("path").join(__dirname, "plugins");
139+
require("fs").readdirSync(pluginsPath).forEach(function (file) {
140+
try {
141+
if (file.endsWith(".js")) {
142+
require("./plugins/" + file)(controller, sparkbot);
143+
debug("plugin loaded: " + file);
144+
}
145+
}
146+
catch (err) {
147+
debug("error, could not load plugin: " + file);
148+
}
149+
});
107150
}
108151
catch (err) {
109-
if (err.code == "MODULE_NOT_FOUND") {
110-
if (file != "utils") {
111-
console.log("Cisco Spark: could not load skill: " + file);
112-
}
152+
if (err.code == "ENOENT") {
153+
debug("plugins directory not present, continuing....");
154+
}
155+
else {
156+
// fail fast
157+
throw err;
113158
}
114159
}
115-
});
116-
117160

118-
//
119-
// Cisco Spark Utilities
120-
//
121-
122-
// Utility to add mentions if Bot is in a 'Group' space
123-
bot.appendMention = function (message, command) {
124-
125-
// if the message is a raw message (from a post message callback such as bot.say())
126-
if (message.roomType && (message.roomType == "group")) {
127-
var botName = bot.botkit.identity.displayName;
128-
return "`@" + botName + " " + command + "`";
161+
// Load skills
162+
try {
163+
var skillsPath = require("path").join(__dirname, "skills");
164+
require("fs").readdirSync(skillsPath).forEach(function (file) {
165+
try {
166+
if (file.endsWith(".js")) {
167+
require("./skills/" + file)(controller);
168+
debug("skill loaded: " + file);
169+
}
170+
}
171+
catch (err) {
172+
debug("error, could not load skill: " + file);
173+
}
174+
});
129175
}
130-
131-
// if the message is a Botkit message
132-
if (message.raw_message && (message.raw_message.data.roomType == "group")) {
133-
var botName = bot.botkit.identity.displayName;
134-
return "`@" + botName + " " + command + "`";
176+
catch (err) {
177+
if (err.code == "ENOENT") {
178+
debug("skills directory not present, aborting....");
179+
}
180+
181+
// fail fast
182+
throw err;
135183
}
184+
});
136185

137-
return "`" + command + "`";
138-
}

configurations/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This place is your chance to customize your bot configuration.
2+
3+
Place here extra Botkit components you want to add, these will be loaded in alphabetical order.

configurations/redis-storage.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Copyright (c) 2017 Cisco Systems
3+
// Licensed under the MIT License
4+
//
5+
6+
/**
7+
* Adds a persistent storage to Redis to BotKit configuration
8+
*
9+
* Setup instructions:
10+
* - add a REDIS_URL env variable pointing to the redis instance
11+
* - add a project dependency to the 'botkit-storage-redis' module, aka "npm install botkit-storage-redis"
12+
* - [optional] customize the configuration below
13+
*/
14+
module.exports = function (configuration) {
15+
16+
if (process.env.REDIS_URL) {
17+
18+
// Initialize Redis storage
19+
var redisConfig = {
20+
// for local dev: redis://127.0.0.1:6379
21+
// if on heroku : redis://h:PASSWORD@ec2-54-86-77-126.compute-1.amazonaws.com:60109
22+
url: process.env.REDIS_URL
23+
24+
// uncomment to add extra global key spaces to store data, example:
25+
//, methods: ['activities']
26+
27+
// uncomment to override the Redis namespace prefix, Defaults to 'botkit:store', example:
28+
//, namespace: 'cisco:devnet'
29+
};
30+
31+
// Create Redis storage for BotKit
32+
try {
33+
var redisStorage = require('botkit-storage-redis')(redisConfig);
34+
35+
configuration.storage = redisStorage;
36+
console.log("Redis storage successfully initialized");
37+
38+
// Note that we did not ping'ed Redis yet
39+
// then a 'ECONNREFUSED' error will be thrown if the Redis can be ping'ed later in the initialization process
40+
// which is fine in a "Fail Fast" strategy
41+
}
42+
catch (err) {
43+
console.log("Could not initialise Redis storage, check the provided Redis URL, err: " + err.message);
44+
}
45+
}
46+
}

configurations/stats_optout.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//
2+
// Copyright (c) 2017 Cisco Systems
3+
// Licensed under the MIT License
4+
//
5+
6+
module.exports = function (configuration) {
7+
8+
// Botkit sends stats to the cloud by default
9+
// Uncomment to stop sending
10+
//configuration["stats_optout"] = true;
11+
}

extensions/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Place in this directory the extensions to be loaded into the bot spawned from BotKit controller.
2+
3+
This is your opportunity to enrich the interactions with Cisco Spark.
4+
by adding extra properties and functions to the `bot` object passed to your skills.
5+
6+
Examples:
7+
- appendMention utility: append the bot name to a command if the current room is a 'Group' space

extensions/append_mention.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// Copyright (c) 2017 Cisco Systems
3+
// Licensed under the MIT License
4+
//
5+
6+
/*
7+
* Utility to add mentions if Bot is in a 'Group' space
8+
*
9+
*/
10+
module.exports = function (bot) {
11+
12+
// Utility to add mentions if Bot is in a 'Group' space
13+
bot.appendMention = function (message, command) {
14+
15+
// if the message is a raw message (from a post message callback such as bot.say())
16+
if (message.roomType && (message.roomType == "group")) {
17+
var botName = bot.botkit.identity.displayName;
18+
return "`@" + botName + " " + command + "`";
19+
}
20+
21+
// if the message is a Botkit message
22+
if (message.raw_message && (message.raw_message.data.roomType == "group")) {
23+
var botName = bot.botkit.identity.displayName;
24+
return "`@" + botName + " " + command + "`";
25+
}
26+
27+
return "`" + command + "`";
28+
}
29+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"description": "Botkit template for Cisco Spark",
3-
"version": "0.6.0",
3+
"version": "0.6.0-plugin1",
44
"main": "bot.js",
55
"scripts": {
66
"start": "node bot.js"
@@ -14,6 +14,7 @@
1414
"license": "MIT",
1515
"dependencies": {
1616
"botkit": "0.6.0",
17+
"botkit-storage-redis": "1.1.0",
1718
"node-env-file": "0.1.8"
1819
},
1920
"repository": {

0 commit comments

Comments
 (0)