├── .env.example ├── .gitignore ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── images │ └── heroku_config-variables.png ├── bot.js ├── features ├── about.js ├── coffee.js ├── favorite.js ├── favorite_restricted.js ├── hello.js ├── help.js ├── loop.js ├── math_quiz.js ├── restricted.js ├── sample_echo.js ├── sample_hears.js ├── source.js ├── storage.js ├── threads.js ├── webex_cards.js ├── webex_features.js ├── welcome.js └── z-fallback.js ├── package-lock.json ├── package.json └── www ├── monitor.png └── stats.png /.env.example: -------------------------------------------------------------------------------- 1 | # 2 | # Store your bot settings in the variables below 3 | # or via environment variables of the same name 4 | # 5 | # DO NOT PUT SECRETS IN THIS FILE IF YOU'RE PUSHING THE CODE TO EXTERNAL REPOS 6 | # 7 | # - you can reference these variables from your code with process.env.SECRET for example 8 | # 9 | # - note that ".env" is formatted like a shell file, so you may (depending on your platform) need to add double quotes if strings contains spaces 10 | # 11 | 12 | # Webex bot account access token 13 | 14 | WEBEX_ACCESS_TOKEN= 15 | 16 | # Internet facing URL where your bot can be reached for incoming webhooks or 17 | # HTTP web server requests. 18 | # Can be empty if WEBSOCKET_EVENTS is True and the Botkit web server is 19 | # not needed, e.g. if not serving images related to the buttons & cards feature 20 | # or the health check URL 21 | 22 | PUBLIC_URL= 23 | 24 | # Use websockets instead of webhooks for incoming Webex events (True/False) 25 | 26 | WEBSOCKET_EVENTS=True 27 | 28 | # Local port where your bot will be started 29 | # defaults to 3000 30 | 31 | PORT=3000 32 | 33 | # Webex JavaScript SDK debug level 34 | # Values: error, warn, info, debug, trace 35 | 36 | WEBEX_LOG_LEVEL=error 37 | 38 | # Storage options - enable only one option (default is in-memory only) 39 | 40 | # Redis storage URL - enable to use Redis for Botkit conversation persistence/scalability 41 | 42 | REDIS_URL= 43 | 44 | # MongoDB storage URL - enable to use MongoDB for Botkit conversation persistence/scalability 45 | 46 | MONGO_URI= 47 | 48 | # Botkit CMS credentials 49 | 50 | CMS_URI= 51 | CMS_TOKEN= 52 | 53 | # Node environment setting 54 | # defaults to development 55 | 56 | NODE_ENV=development 57 | 58 | # Bot meta info - displayed when browsing the bot healthcheck/public URL 59 | 60 | PLATFORM=Webex 61 | CODE=https://github.com/CiscoDevNet/botkit-template 62 | #OWNER= 63 | #SUPPORT= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | .eslintrc.js 4 | .env 5 | 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/bot.js" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## v1.0 (2019-11-23) 4 | 5 | - Updates to Botkit 4.5: extensive updates to bot.js and all samples 6 | - Modify project structure based on botkit-generator Yeoman template 7 | - `skills/`, `plugins/`, `configurations/`, `extensions/` removed in favor of `features/` 8 | - Include Redis support in main branch 9 | - Remove `redis` and `plugin` branches 10 | - Add `hello.js` sample 11 | - Rename various samples 12 | - Add command/help array to controller, autogenerate help menu in `help.js` 13 | 14 | ## v0.9 (2018-12-12): botkit framework update 15 | 16 | - updating to Bokit v0.7 17 | 18 | ## v0.8 (2018-06-07): metadata updates 19 | 20 | - fix for botcommons 'platform' (formely 'plaform') 21 | - turning botcommons and healtcheck JSON to snake_case (formely kebab-case) 22 | 23 | ## v0.7 (2018-05-17): reflecting Webex Teams rebrand 24 | 25 | - introducing ACCESS_TOKEN env variable 26 | - backward compatibility for SPARK_TOKEN env variable 27 | - documentation updates (removing spark mentions) 28 | - added popular skills from convos@sparkbot.io 29 | 30 | ## v0.6 (2017-11-17): legacy version for Cisco Spark 31 | 32 | - configuration through environment variables or hard-coded values in the .env file 33 | - skills: organize your bot behaviours by placing 'commands', 'conversations' and 'events' in the skills directory 34 | - user experience: the template comes with ready-to-use skills: a 'welcome' invite, as well as 'help' and 'fallback' commands. 35 | - healthcheck: easily check that everything goes well by hitting the ping endpoint automatically exposed. 36 | - metadata: expose extra info via command and on a public address so that Spark users can inquire on Bot Author / Legal mentions / Healthcheck endpoint... 37 | - mentions: the appendMention utility function helps Spark users remind to mention the bot in Group spaces. 38 | popular cloud providers: the bot self-configures when run on Glitch, and also Heroku (if dyno-metadata are installed for Heroku). 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cisco DevNet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Botkit template 2 | 3 | This project implements a Botkit + Webex adapter bot, based on the [generator-botkit](https://www.npmjs.com/package/generator-botkit) Yoeman template, providing a few extra good-practice features, plus several interesting samples: 4 | 5 | - Optionally use Webex Node.js SDK websockets for incoming events and messages, instead of webhooks 6 | 7 | - A 'health check' URL: check bot availability, uptime and metadata by browsing to the bot's public URL 8 | 9 | - Quality-of-life features: fallback/catch-all module; welcome message when user joins a space 10 | 11 | - 'Help' command auto-generation function 12 | 13 | - Redis/MongoDB storage support for persistent/scalable storage of conversation state 14 | 15 | - checkAddMention() function to automatically format bot commands for 1:1 or group space usage 16 | 17 | ## Websockets vs. Webhooks 18 | 19 | Most Botkit features can be implemented by using the Webex JS SDK websockets functionality, which establishes a persistent connection to the Webex cloud for outbound and inbound messages/events. 20 | 21 | Webex also supports traditional HTTP webhooks for messages/events, which requires that your bot be accessible via a publically reachable URL. A public URL is also needed if your bot will be serving any web pages/files, e.g. images associated with the cards and buttons feature or the health check URL. 22 | 23 | - If you don't need to serve buttons and cards images, you can set the environment variable `WEBSOCKET_EVENTS=True` and avoid the need for a public URL 24 | - If you are implementing buttons & cards, you will need a public URL (e. g. by using a service like Ngrok, or hosting your bot in the cloud) - configure this via the `PUBLIC_URL` environment variable 25 | 26 | ## How to run (local machine) 27 | 28 | Assuming you plan to us [ngrok](https://ngrok.com) to give your bot a publically available URL (optional, see above), you can run this template in a jiffy: 29 | 30 | 1. Clone this repo: 31 | 32 | ```sh 33 | git clone https://github.com/CiscoDevNet/botkit-template.git 34 | 35 | cd botkit-template 36 | ``` 37 | 38 | 1. Install the Node.js dependencies: 39 | 40 | ```sh 41 | npm install 42 | ``` 43 | 44 | 1. Create a Webex bot account at ['Webex for Developers'](https://developer.webex.com/my-apps/new/bot), and note/save your bot's access token 45 | 46 | 1. Launch Ngrok to expose port 3000 of your local machine to the internet: 47 | 48 | ```sh 49 | ngrok http 3000 50 | ``` 51 | 52 | Note/save the 'Forwarding' HTTPS (not HTTP) address that ngrok generates 53 | 54 | 1. Rename the `env.example` file to `.env`, then edit to configure the settings and info for your bot. 55 | 56 | >Note: you can also specify any of these settings via environment variables (which will take precedent over any settings configured in the `.env` file) - often preferred in production environments. 57 | 58 | To successfully run all of the sample features, you'll need to specify at minimum a `PUBLIC_URL` (ngrok HTTPS forwarding URL), and a `WEBEX_ACCESS_TOKEN` (Webex bot access token). 59 | 60 | >If running on Glitch.me or Heroku (with [Dyno Metadata](https://devcenter.heroku.com/articles/dyno-metadata) enbaled), the `PUBLIC_URL` will be auto-configured. 61 | 62 | Additional values in the `.env` file (like `OWNER` and `CODE`) are used to populate the healthcheck URL meta-data. 63 | 64 | Be sure to save the `.env` file! 65 | 66 | 1. You're ready to run your bot: 67 | 68 | ```sh 69 | node bot.js 70 | ``` 71 | 72 | ## Quick start on Glitch.me 73 | 74 | * Click [![Remix on Glitch](https://cdn.glitch.com/2703baf2-b643-4da7-ab91-7ee2a2d00b5b%2Fremix-button.svg)](https://glitch.com/edit/#!/import/github/CiscoDevNet/botkit-template) 75 | 76 | * Delete the `.env` file that Glitch created automatically 77 | 78 | * Rename `.env.example` to `.env`, then open it for editing. 79 | 80 | Find the `WEBEX_ACCESS_TOKEN` variable, paste in your bot's access token 81 | 82 | **Optional**: enter appropriate info in the "Bot meta info..." section 83 | 84 | >Note that, thanks to the Glitch `PROJECT_DOMAIN` env variable, you do not need to add a `PUBLIC_URL` variable pointing to your app domain 85 | 86 | You bot is all set, responding in 1-1 and 'group' spaces, and sending a welcome message when added to a space! 87 | 88 | You can verify the bot is up and running by browsing to its healthcheck URL (i.e. the app domain.) 89 | 90 | ## Quick start on Heroku 91 | 92 | * Create a new project pointing to this repo. 93 | 94 | * Open your app's **Settings** tab, and reveal your **Config Vars** 95 | 96 | * Add a `WEBEX_ACCESS_TOKEN` variable with your bot's access token as value 97 | 98 | * Add a `PUBLIC_URL` variable pointing to your app's Heroku URL 99 | 100 | >If your app is using [Dyno Metadata](https://devcenter.heroku.com/articles/dyno-metadata), the public URL will be detected automatically 101 | 102 | ![](assets/images/heroku_config-variables.png) 103 | 104 | * In the upper right under the **More** dropdown, select **Restart all dynos** 105 | 106 | You bot is all set! You can invite it to 1-1 and 'group' spaces, see it sending a welcome message when added, and responding to commands (try `help`.) 107 | 108 | You can always verify the bot is operational by browsing to its healthcheck URL (i.e. the app domain.) -------------------------------------------------------------------------------- /assets/images/heroku_config-variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/botkit-template/54807d4079aa9aad2817d2256406bbc7de9611ff/assets/images/heroku_config-variables.png -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Cisco and/or its affiliates. 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to deal 4 | // in the Software without restriction, including without limitation the rights 5 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | // copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // The above copyright notice and this permission notice shall be included in all 9 | // copies or substantial portions of the Software. 10 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 11 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 12 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 13 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 14 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 15 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 16 | // SOFTWARE. 17 | // __ __ ___ ___ 18 | // |__) / \ | |__/ | | 19 | // |__) \__/ | | \ | | 20 | 21 | // This is the main file for the template bot. 22 | 23 | // Load process.env values from .env file 24 | require('dotenv').config(); 25 | 26 | if (!process.env.WEBEX_ACCESS_TOKEN) { 27 | console.log( '\n-->Token missing: please provide a valid Webex user or bot access token in .env or via WEBEX_ACCESS_TOKEN environment variable'); 28 | process.exit(1); 29 | } 30 | 31 | // Read public URL from env, 32 | // if not specified, try to infer it from public cloud platforms environments 33 | var public_url = process.env.PUBLIC_URL; 34 | 35 | if (!public_url) { 36 | // Heroku hosting: available if dyno metadata are enabled, https://devcenter.heroku.com/articles/dyno-metadata 37 | if (process.env.HEROKU_APP_NAME) { 38 | public_url = 'https://' + process.env.HEROKU_APP_NAME + '.herokuapp.com'; 39 | } 40 | 41 | // Glitch hosting 42 | if (process.env.PROJECT_DOMAIN) { 43 | public_url = 'https://' + process.env.PROJECT_DOMAIN + '.glitch.me'; 44 | } 45 | } 46 | 47 | var storage; 48 | 49 | if (process.env.REDIS_URL) { 50 | 51 | const redis = require('redis'); 52 | const { RedisDbStorage } = require('botbuilder-storage-redis'); 53 | 54 | // Initialize redis client 55 | const redisClient = redis.createClient(process.env.REDIS_URL, { prefix: 'bot-storage:' }); 56 | storage = new RedisDbStorage(redisClient, 3600); // Keep redis data for 3600 seconds 57 | } 58 | 59 | if (process.env.MONGO_URI) { 60 | 61 | const { MongoDbStorage } = require('botbuilder-storage-mongodb'); 62 | 63 | storage = new MongoDbStorage({ url: process.env.MONGO_URI }) 64 | } 65 | 66 | // Create Webex Adapter 67 | // const uuidv4 = require('uuid/v4'); 68 | const { v4: uuidv4 } = require('uuid'); 69 | const { WebexAdapter } = require('botbuilder-adapter-webex'); 70 | 71 | // If PUBLIC_URL not configured, supply a dummy public_address 72 | // If using websockets, don't supply a secret or Botkit will try/fail to 73 | // validate non-existent secret field for incoming events 74 | const adapter = new WebexAdapter({ 75 | 76 | access_token: process.env.WEBEX_ACCESS_TOKEN, 77 | public_address: public_url ? public_url : 'http://127.0.0.1', 78 | secret: ( process.env.WEBSOCKET_EVENTS == 'True' ) ? null : uuidv4() 79 | }); 80 | 81 | const { Botkit } = require('botkit'); 82 | 83 | const controller = new Botkit({ 84 | 85 | webhook_uri: '/api/messages', 86 | adapter: adapter, 87 | storage 88 | }); 89 | 90 | // Create Botkit controller 91 | 92 | if (process.env.CMS_URI) { 93 | const { BotkitCMSHelper } = require('botkit-plugin-cms'); 94 | controller.usePlugin(new BotkitCMSHelper({ 95 | uri: process.env.CMS_URI, 96 | token: process.env.CMS_TOKEN 97 | })); 98 | }; 99 | 100 | // Once the bot has booted up its internal services, you can use them to do stuff. 101 | const path = require('path'); 102 | 103 | // Express response stub to supply to processWebsocketActivity 104 | // Luckily, the Webex adapter doesn't do anything meaningful with it 105 | class responseStub { 106 | status(){} 107 | end(){} 108 | } 109 | 110 | function processWebsocketActivity( event ) { 111 | // Express request stub to fool the Activity processor 112 | let requestStub = {}; 113 | // Event details are expected in a 'body' property 114 | requestStub.body = event; 115 | 116 | // Hand the event off to the Botkit activity processory 117 | controller.adapter.processActivity( requestStub, new responseStub, controller.handleTurn.bind( controller ) ) 118 | } 119 | 120 | controller.ready( async () => { 121 | // load developer-created custom feature modules 122 | controller.loadModules(path.join(__dirname, 'features')); 123 | 124 | if ( ( !public_url ) && ( process.env.WEBSOCKET_EVENTS !== 'True' ) ) { 125 | console.log( '\n-->No inbound event channel available. Please configure at least one of PUBLIC_URL and/or WEBSOCKET_EVENTS' ); 126 | process.exit( 1 ); 127 | } 128 | 129 | if ( public_url ) { 130 | // Make the app public_url available to feature modules, for use in adaptive card content links 131 | controller.public_url = public_url; 132 | } 133 | 134 | if ( process.env.WEBSOCKET_EVENTS == 'True' ) { 135 | 136 | await controller.adapter._api.memberships.listen(); 137 | controller.adapter._api.memberships.on( 'created', ( event ) => processWebsocketActivity( event ) ); 138 | controller.adapter._api.memberships.on( 'updated', ( event ) => processWebsocketActivity( event ) ); 139 | controller.adapter._api.memberships.on( 'deleted', ( event ) => processWebsocketActivity( event ) ); 140 | 141 | await controller.adapter._api.messages.listen(); 142 | controller.adapter._api.messages.on('created', ( event ) => processWebsocketActivity( event ) ); 143 | controller.adapter._api.messages.on('deleted', ( event ) => processWebsocketActivity( event ) ); 144 | 145 | await controller.adapter._api.attachmentActions.listen(); 146 | controller.adapter._api.attachmentActions.on('created', ( event ) => processWebsocketActivity( event ) ); 147 | 148 | // Remove unnecessary auto-created webhook subscription 149 | await controller.adapter.resetWebhookSubscriptions(); 150 | 151 | console.log( 'Using websockets for incoming messages/events'); 152 | } 153 | else { 154 | // Register attachmentActions webhook 155 | controller.adapter.registerAdaptiveCardWebhookSubscription( controller.getConfig( 'webhook_uri' ) ); 156 | } 157 | }); 158 | 159 | if (public_url) { 160 | controller.publicFolder('/www', __dirname + '/www'); 161 | 162 | controller.webserver.get('/', (req, res) => { 163 | res.send(JSON.stringify(controller.botCommons, null, 4)); 164 | }); 165 | 166 | console.log('Health check available at: ' + public_url); 167 | } 168 | 169 | controller.commandHelp = []; 170 | 171 | controller.checkAddMention = function (roomType, command) { 172 | 173 | var botName = adapter.identity.displayName; 174 | 175 | if (roomType === 'group') { 176 | 177 | return `\`@${botName} ${command}\`` 178 | } 179 | 180 | return `\`${command}\`` 181 | } 182 | -------------------------------------------------------------------------------- /features/about.js: -------------------------------------------------------------------------------- 1 | // 2 | // Health check info 3 | // 4 | module.exports = function (controller) { 5 | 6 | controller.botCommons = { 7 | 8 | healthCheck: process.env.PUBLIC_ADDRESS, 9 | upSince: new Date( Date.now() ).toGMTString(), 10 | botName: controller.adapter.identity.displayName, 11 | botVersion: 'v' + require( '../package.json' ).version, 12 | owner: process.env.OWNER, 13 | support: process.env.SUPPORT, 14 | botkitVersion: controller.version, 15 | platform: process.env.PLATFORM, 16 | code: process.env.CODE 17 | } 18 | 19 | controller.hears( 'about', 'message,direct_message', async ( bot, message ) => { 20 | 21 | let markDown = '```json\n'; 22 | markDown += JSON.stringify( controller.botCommons, null, 4 ); 23 | markDown += '\n```' 24 | await bot.reply( message, { markdown: markDown } ); 25 | }); 26 | 27 | controller.commandHelp.push( { command: 'about', text: 'Display bot metadata' } ); 28 | 29 | } -------------------------------------------------------------------------------- /features/coffee.js: -------------------------------------------------------------------------------- 1 | // 2 | // Illustrates a muti-threaded conversation 3 | // 4 | // Q: "How about some coffee (yes / no / cancel)" 5 | // A: no 6 | // Q: "What would you like to drink instead..?" 7 | // A: Coke 8 | // 9 | const { BotkitConversation } = require( 'botkit' ); 10 | 11 | module.exports = function (controller) { 12 | 13 | const convo = new BotkitConversation( 'coffee_chat', controller ); 14 | 15 | convo.ask( 'How about some coffee? (yes / no / cancel)', [ 16 | { 17 | pattern: 'yes|ya|yeah|sure|oui|si', 18 | handler: async ( response, convo ) => { 19 | 20 | await convo.gotoThread( 'confirm' ); 21 | } 22 | }, 23 | { 24 | pattern: 'no|neh|non|nein|na|birk', 25 | handler: async ( response, convo ) => { 26 | 27 | await convo.gotoThread( 'ask_drink' ); 28 | } 29 | }, 30 | { 31 | pattern: 'cancel|stop|exit', 32 | handler: async ( response, convo ) => { 33 | 34 | await convo.gotoThread( 'cancel' ); 35 | } 36 | }, 37 | { 38 | default: true, 39 | handler: async ( response, convo ) => { 40 | await convo.gotoThread( 'bad_response' ); 41 | } 42 | } 43 | ]) 44 | 45 | convo.addMessage( { 46 | text: 'Ah...I seem to be fresh out!', 47 | action: 'complete' 48 | }, 'confirm' ); 49 | 50 | convo.addMessage( { 51 | text: 'Got it...cancelling', 52 | action: 'complete' 53 | }, 'cancel' ); 54 | 55 | convo.addMessage( { 56 | text: 'Sorry, I did not understand!\nTip: try "yes", "no", or "cancel"', 57 | action: 'default' 58 | }, 'bad_response' ); 59 | 60 | // Thread: ask for a drink 61 | convo.addQuestion( 'What would you like to drink instead..?', [], 'statedDrink', 'ask_drink' ); 62 | convo.addMessage( 'Excellent! I like {{ vars.statedDrink }} too', 'ask_drink' ); 63 | 64 | controller.addDialog( convo ); 65 | 66 | controller.hears( 'coffee', 'message,direct_message', async ( bot, message ) => { 67 | 68 | await bot.beginDialog( 'coffee_chat' ); 69 | }); 70 | 71 | controller.commandHelp.push( { command: 'coffee', text: 'Simple dialog example with threads' } ); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /features/favorite.js: -------------------------------------------------------------------------------- 1 | // 2 | // Simplest use of Botkit's conversation system 3 | // 4 | const { BotkitConversation } = require( 'botkit' ); 5 | 6 | module.exports = function( controller ) { 7 | 8 | const convo = new BotkitConversation( 'fav_color_chat', controller ); 9 | 10 | convo.say( 'This is a Botkit conversation sample.' ); 11 | convo.ask( 12 | 'What is your favorite color?', 13 | async ( answer, convo, bot ) => {}, 14 | 'stated_color' 15 | ); 16 | convo.say( `Cool, I like {{ vars.stated_color }} too!` ); 17 | 18 | controller.addDialog( convo ); 19 | 20 | controller.hears( 'favorite', 'message,direct_message', async ( bot, message ) => { 21 | 22 | await bot.beginDialog( 'fav_color_chat' ); 23 | }); 24 | 25 | controller.commandHelp.push( { command: 'favorite', text: 'Pick a favorite color (Botkit conversations)' } ); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /features/favorite_restricted.js: -------------------------------------------------------------------------------- 1 | // 2 | // Forces the user to pick among a predefined list of options 3 | // 4 | const { BotkitConversation } = require( 'botkit' ); 5 | 6 | module.exports = function (controller) { 7 | 8 | const convo = new BotkitConversation( 'guess_chat', controller ); 9 | 10 | convo.ask( 'Can you guess one of my favorite colors?', [ 11 | { 12 | pattern: '^blue|green|pink|red|yellow$', 13 | handler: async ( response, convo ) => { 14 | await convo.gotoThread( 'success' ); 15 | } 16 | }, 17 | { 18 | default: true, 19 | handler: async ( response, convo ) => { 20 | await convo.gotoThread( 'failure' ) 21 | } 22 | } 23 | ], 'guessedColor'); 24 | 25 | convo.addMessage( { 26 | text: 'Wow! You guessed right, {{ vars.guessedColor }} is one of my favorites', 27 | action: 'complete' 28 | }, 'success' ); 29 | 30 | convo.addMessage( { 31 | text: 'Nope...', 32 | action: 'default' 33 | }, 'failure' ); 34 | 35 | controller.addDialog( convo ); 36 | 37 | controller.hears( 'guess', 'message,direct_message', async (bot, message) => { 38 | 39 | await bot.beginDialog( 'guess_chat' ); 40 | }); 41 | 42 | 43 | controller.commandHelp.push( { command: 'guess', text: 'Conversation example - guess from a restricted list of options' } ); 44 | }; 45 | -------------------------------------------------------------------------------- /features/hello.js: -------------------------------------------------------------------------------- 1 | // 2 | // Respond to various 'hello' words, attach file by URL and from local file system 3 | var fs = require('fs'); 4 | 5 | module.exports = function( controller ) { 6 | 7 | controller.hears( [ 'hi','hello','howdy','hey','aloha','hola','bonjour','oi' ], 'message,direct_message', async ( bot,message ) => { 8 | 9 | await bot.reply( message,'Greetings!' ); 10 | await bot.reply( message, { markdown: 'Try `help` to see available commands' } ); 11 | }); 12 | 13 | controller.hears( 'url', 'message,direct_message', async ( bot,message ) => { 14 | 15 | await bot.reply( message, { 16 | text: 'Aww!', 17 | files: [ 'https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Youngkitten.JPG/220px-Youngkitten.JPG' ] 18 | }); 19 | }) 20 | 21 | controller.hears( 'local', 'message,direct_message' , async ( bot,message ) => { 22 | await bot.reply( message, { 23 | text: 'The source code', 24 | files: [ fs.createReadStream( './bot.js' ) ] 25 | }) 26 | }) 27 | 28 | controller.commandHelp.push( { command: 'hello', text: 'Greetings!' } ); 29 | controller.commandHelp.push( { command: 'url', text: 'Attach a file via URL' } ); 30 | controller.commandHelp.push( { command: 'local', text: 'Attach a file from the local file system' } ); 31 | 32 | } -------------------------------------------------------------------------------- /features/help.js: -------------------------------------------------------------------------------- 1 | // 2 | // Command: help 3 | // 4 | module.exports = function (controller) { 5 | 6 | controller.hears( 'help', 'message,direct_message', async ( bot, message ) => { 7 | 8 | let markDown = '**Available commands:** \n'; 9 | 10 | controller.commandHelp.sort( ( a,b ) => { 11 | 12 | return ( ( a.command < b.command ) ? -1 : ( ( a.command > b.command ) ? 1 : 0 )); 13 | }); 14 | 15 | controller.commandHelp.forEach( element => { 16 | 17 | markDown += `**${ controller.checkAddMention( message.roomType, element.command ) }**: ${ element.text } \n` 18 | }); 19 | 20 | await bot.reply( message, { markdown: markDown } ); 21 | 22 | // text += "\n- " + bot.appendMention(message, "storage") + ": store picked color as a user preference"; 23 | }); 24 | 25 | controller.commandHelp.push( { command: 'help', text: 'Show available commands/descriptions' } ); 26 | } 27 | -------------------------------------------------------------------------------- /features/loop.js: -------------------------------------------------------------------------------- 1 | // 2 | // Example of a conversation with a menu that loops until explicitly stopped 3 | // 4 | 5 | const { BotkitConversation } = require( 'botkit' ); 6 | 7 | module.exports = function (controller) { 8 | 9 | const convo = new BotkitConversation( 'loop_chat', controller ); 10 | 11 | let question = 'Here are a few proposed DevNet activities:\n'; 12 | question += ' 1. Join a Community Of Interest: ( communities )\n'; 13 | question += ' 2. Take a Learning Lab: ( labs )\n'; 14 | question += ' 3. Check Upcoming Events: ( events )\n'; 15 | question += 'What would you like to see?\n(type a number, a ( keyword) or "stop")'; 16 | 17 | convo.ask( question, [ 18 | { 19 | pattern: '1|community|communities', 20 | handler: async (response, convo, bot) => { 21 | return await convo.gotoThread( 'menu_1' ); 22 | } 23 | }, 24 | { 25 | pattern: '2|lab|track|learn', 26 | handler: async (response, convo, bot) => { 27 | return await convo.gotoThread( 'menu_2' ); 28 | } 29 | }, 30 | { 31 | pattern: '3|event|express', 32 | handler: async (response, convo, bot) => { 33 | return await convo.gotoThread( 'menu_3' ); 34 | } 35 | }, 36 | { 37 | pattern: 'cancel|stop', 38 | handler: async (response, convo, bot) => { 39 | return await convo.gotoThread( 'action_cancel' ); 40 | } 41 | }, 42 | { 43 | default: true, 44 | handler: async (response, convo, bot) => { 45 | await bot.say( 'Unrecognized response... \nTry again!' ); 46 | return await convo.repeat(); 47 | }, 48 | } 49 | ]); 50 | 51 | // Menu option 1) 52 | convo.addMessage({ 53 | text: 'Excellent choice: now discover the DevNet communities online, ' + 54 | 'and pick your favorite: https://developer.cisco.com/site/coi/\n\n', 55 | action: 'default' 56 | }, 'menu_1'); 57 | 58 | // Menu option 2) 59 | convo.addMessage({ 60 | text: 'Learnings Labs are step-by-step tutorials. ' + 61 | 'They are grouped into tracks to help you on your learning journey. ' + 62 | 'Browse through the learnings tracks here: https://learninglabs.cisco.com/login\n\n', 63 | action: 'default' 64 | }, 'menu_2'); 65 | 66 | // Menu option 3) 67 | convo.addMessage({ 68 | text: 'Nothing like meeting in person at a conference, training or a hackathon. ' + 69 | 'Check the list of DevNet events: https://developer.cisco.com/site/devnet/events-contests/events/\n\n', 70 | action: 'default' 71 | }, 'menu_3'); 72 | 73 | // Cancel 74 | convo.addMessage({ 75 | text: 'Got it, cancelling...', 76 | action: 'complete', 77 | }, 'action_cancel'); 78 | 79 | 80 | controller.addDialog( convo ); 81 | 82 | controller.hears( 'loop', 'message,direct_message', async ( bot, message ) => { 83 | 84 | await bot.beginDialog( 'loop_chat' ); 85 | }); 86 | 87 | controller.commandHelp.push( { command: 'loop', text: 'Example of a conversation that loops until explicitly stopped' } ); 88 | 89 | }; 90 | 91 | -------------------------------------------------------------------------------- /features/math_quiz.js: -------------------------------------------------------------------------------- 1 | // 2 | // Quiz: example of a muti-threaded conversation with timeout 3 | // 4 | //TODO: implement timeout mechanism for Botkit 4.5 5 | 6 | const { BotkitConversation } = require( 'botkit' ); 7 | 8 | module.exports = function (controller) { 9 | 10 | const convo = new BotkitConversation( 'math_chat', controller ); 11 | 12 | convo.ask( 'Ready for a challenge? (yes/no/cancel)', [ 13 | { 14 | pattern: 'yes|ya|yeah|sure|oui|si', 15 | handler: async ( response, convo ) => { 16 | 17 | convo.gotoThread( 'quiz' ); 18 | } 19 | }, 20 | { 21 | pattern: 'no|neh|non|na|birk|cancel|stop|exit', 22 | handler: async ( response, convo ) => { 23 | 24 | await convo.gotoThread( 'cancel' ); 25 | }, 26 | }, 27 | { 28 | default: true, 29 | handler: async ( response, convo ) => { 30 | await convo.gotoThread( 'bad_answer' ); 31 | } 32 | } 33 | ]); 34 | 35 | // Thread: bad response 36 | convo.addMessage({ 37 | text: 'Sorry, I did not understand...', 38 | action: 'default', // goes back to the thread's current state, where the question is not answered 39 | }, 'bad_answer' ); 40 | 41 | // Thread: cancel 42 | convo.addMessage({ 43 | text: 'Got it, cancelling...', 44 | action: 'stop', // this marks the converation as unsuccessful 45 | }, 'cancel'); 46 | 47 | // Thread: quiz 48 | 49 | convo.addMessage( 'Let\'s go...', 'quiz' ); 50 | 51 | let challenge = pickChallenge(); 52 | 53 | convo.addQuestion( { 54 | text: async ( template, vars ) => { 55 | 56 | return `Question: ${ challenge.question }` } 57 | }, 58 | [ 59 | { 60 | pattern: 'cancel|stop|exit', 61 | handler: async ( response, convo ) => { 62 | 63 | await convo.gotoThread( 'cancel' ); 64 | } 65 | }, 66 | { 67 | default: true, 68 | handler: async (response, convo) => { 69 | 70 | if ( response == challenge.answer){ 71 | challenge = pickChallenge(); 72 | await convo.gotoThread( 'success' ) 73 | } 74 | else { 75 | await convo.gotoThread( 'wrong_answer' ); 76 | } 77 | } 78 | } 79 | ], {}, 'quiz'); 80 | 81 | // Thread: quiz - success 82 | convo.addMessage( 'Congrats, you did it!', 'success'); 83 | 84 | // Thread: quiz - missed 85 | // convo.addMessage( 'Time elapsed! you missed it, sorry.', 'missed' ); //TODO 86 | 87 | // Thread: quiz - wrong answer 88 | convo.addMessage({ 89 | text: 'Sorry, wrong answer. Try again!', 90 | action: 'quiz', // goes back to the thread's current state, where the question is not answered 91 | }, 'wrong_answer'); 92 | 93 | controller.addDialog( convo ); 94 | 95 | function pickChallenge() { 96 | var a = Math.round(Math.random() * 5) + 4; 97 | var b = Math.round(Math.random() * 5) + 4; 98 | return { 99 | question: `${ a } x ${ b } =`, 100 | answer: `${ a*b }` 101 | } 102 | } 103 | 104 | controller.hears( 'math', 'message,direct_message', async ( bot, message ) => { 105 | 106 | await bot.beginDialog( 'math_chat' ); 107 | }); 108 | 109 | controller.commandHelp.push( { command: 'math', text: 'Conversation/thread example implementing a math quiz' } ); 110 | 111 | } -------------------------------------------------------------------------------- /features/restricted.js: -------------------------------------------------------------------------------- 1 | const { BotkitConversation } = require( 'botkit' ); 2 | 3 | module.exports = function( controller ) { 4 | 5 | const convo = new BotkitConversation( 'flag_colors', controller ); 6 | 7 | convo.ask( 'Can you name one of the colors on the flag of the United States of America..?', [ 8 | { 9 | pattern: '^red|white|blue$', 10 | handler: async (response, convo, bot) => { 11 | await bot.say( `Correct! "${ response }" is one of the three colors on the US flag` ); 12 | }, 13 | }, 14 | { 15 | default: true, 16 | handler: async (response, convo, bot) => { 17 | await bot.say( 'Incorrect :/ \nTry again!' ); 18 | await convo.repeat(); 19 | }, 20 | } 21 | ]); 22 | 23 | controller.addDialog( convo ); 24 | 25 | controller.hears( 'quiz', 'message,direct_message', async ( bot, message ) => { 26 | 27 | await bot.beginDialog( 'flag_colors' ); 28 | }); 29 | 30 | controller.commandHelp.push( { command: 'quiz', text: 'Conversation/thread example implementing a flag quiz' } ); 31 | } -------------------------------------------------------------------------------- /features/sample_echo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | module.exports = function(controller) { 7 | 8 | controller.hears('sample','message,direct_message', async (bot, message) => { 9 | await bot.reply(message, 'I heard a sample message.'); 10 | }); 11 | 12 | controller.on('message', async (bot, message) => { 13 | console.log("MESSAGE from: " + message.personEmail); 14 | await bot.reply(message, `Echo: ${ message.text }`); 15 | }); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /features/sample_hears.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | module.exports = function(controller) { 6 | 7 | // use a function to match a condition in the message 8 | controller.hears(async (message) => message.text && message.text.toLowerCase() === 'foo', ['message'], async (bot, message) => { 9 | await bot.reply(message, 'I heard "foo" via a function test'); 10 | }); 11 | 12 | // use a regular expression to match the text of the message 13 | controller.hears(new RegExp(/^\d+$/), ['message','direct_message'], async function(bot, message) { 14 | await bot.reply(message,{ text: 'I heard a number using a regular expression.' }); 15 | }); 16 | 17 | // match any one of set of mixed patterns like a string, a regular expression 18 | controller.hears(['allcaps', new RegExp(/^[A-Z\s]+$/)], ['message','direct_message'], async function(bot, message) { 19 | await bot.reply(message,{ text: 'I HEARD ALL CAPS!' }); 20 | }); 21 | 22 | } -------------------------------------------------------------------------------- /features/source.js: -------------------------------------------------------------------------------- 1 | // 2 | // Displays the code of the specified skill 3 | // 4 | const fs = require('fs'); 5 | const { BotkitConversation } = require( 'botkit' ); 6 | 7 | module.exports = function ( controller ) { 8 | 9 | const convo = new BotkitConversation( 'source_chat', controller ); 10 | 11 | let sourceFiles = fs.readdirSync( 'features' ).filter( ( value, index, arr ) => { 12 | return value.endsWith( '.js' ); 13 | } ); 14 | 15 | let question = 'Select a "feature" JavaScript file by # to see the source code (or "cancel"): \n'; 16 | let respPattern = '^'; 17 | 18 | sourceFiles.forEach( ( value, index, arr ) => { 19 | 20 | question += `${ index + 1 }. ${ value } \n`; 21 | 22 | respPattern += `${ index + 1 }`; 23 | respPattern += ( index = ( arr.length - 1 ) ) ? '|' : '$'; 24 | }) 25 | 26 | convo.ask( question, [ 27 | { 28 | pattern: respPattern, 29 | handler: async ( response, convo, bot ) => { 30 | 31 | await bot.say( { markdown: getSource( response ) } ); 32 | await convo.stop(); 33 | } 34 | }, 35 | { 36 | pattern: 'cancel', 37 | handler: async ( response, convo ) => { 38 | 39 | await convo.gotoThread( 'cancel' ); 40 | } 41 | }, 42 | { 43 | default: true, 44 | handler: async ( response, convo ) => { 45 | 46 | await convo.gotoThread( 'invalid' ); 47 | } 48 | } 49 | ], 'selection' ); 50 | 51 | convo.addMessage( { 52 | text: 'Got it, cancelling...', 53 | action: 'complete' 54 | }, 'cancel' ); 55 | 56 | convo.addMessage( { 57 | text: 'Unrecognized response!', 58 | action: 'default' 59 | }, 'invalid' ); 60 | 61 | controller.addDialog( convo ); 62 | 63 | controller.hears( 'source', 'message,direct_message', async ( bot, message ) => { 64 | 65 | await bot.beginDialog( 'source_chat' ); 66 | }); 67 | 68 | function getSource( selection ) { 69 | 70 | let selectedFile = sourceFiles[ parseInt( selection ) - 1 ]; 71 | let normalizedPath = require( 'path' ).join( __dirname, selectedFile ); 72 | 73 | let markDown = '```javascript\n'; 74 | markDown += fs.readFileSync( normalizedPath ).toString(); 75 | markDown += '\n```'; 76 | 77 | return markDown; 78 | } 79 | 80 | controller.commandHelp.push( { command: 'source', text: 'Show the source code of the available "feature" files' } ); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /features/storage.js: -------------------------------------------------------------------------------- 1 | // 2 | // Demonstrate CRUD operations on conversation variables, to test Botkit storage 3 | // Note: Botkit uses in-memory (non-persistent) storage by default, configure REDIS_URL 4 | // or MONGO_URI in .env to enable persistent/scalable storage 5 | 6 | const { BotkitConversation } = require( 'botkit' ); 7 | 8 | module.exports = function (controller) { 9 | 10 | const convo = new BotkitConversation( 'storage_chat', controller ); 11 | 12 | convo.say( `If Botkit persistent storage is configured, I can remember your favorite color even if I'm restarted!` ); 13 | convo.ask( `What is your favorite color (don't forget to @mention me if in a group Space?)`, [], 'statedColor' ); 14 | 15 | convo.say( 'Your favorite color is: {{ vars.statedColor }}' ); 16 | convo.ask( `You can now stop and restart the bot app...\nWhen the bot app is restarted, enter "ready" to test my powers of recall (don't forget to @mention me if in a group Space!)\n`, [], {} ); 17 | convo.say( 'Your favorite color is: {{ vars.statedColor }}' ); 18 | 19 | controller.addDialog( convo ); 20 | 21 | controller.hears( 'storage', 'message,direct_message', async ( bot, message ) => { 22 | 23 | await bot.beginDialog( 'storage_chat' ); 24 | 25 | }); 26 | 27 | controller.commandHelp.push( { command: 'storage', text: 'Example showing use of Redis/MongoDB to persist conversation data' } ); 28 | 29 | } -------------------------------------------------------------------------------- /features/threads.js: -------------------------------------------------------------------------------- 1 | const { BotkitConversation } = require( 'botkit' ); 2 | 3 | module.exports = function( controller ) { 4 | 5 | const convo = new BotkitConversation( 'states_quiz', controller ); 6 | 7 | convo.ask( 'Can you name one of the last three states admitted to the U.S.?', [ 8 | { 9 | pattern: '^Arizona|Alaska|Hawaii$', 10 | handler: async (response, convo, bot) => { 11 | await convo.setVar( 'guess', response ); 12 | await convo.gotoThread( 'correct' ); 13 | }, 14 | }, 15 | { 16 | default: true, 17 | handler: async (response, convo, bot) => { 18 | await convo.gotoThread( 'incorrect' ); 19 | }, 20 | } 21 | ]); 22 | 23 | convo.addMessage({ 24 | text: 'Correct! "{{ vars.guess }}" is one of the last three US states', 25 | action: 'complete' 26 | }, 'correct' ); 27 | 28 | convo.addMessage({ 29 | text: 'Incorrect :/ \nTry again!', 30 | action: 'repeat', 31 | }, 'incorrect'); 32 | 33 | controller.addDialog( convo ); 34 | 35 | controller.hears( 'states', 'message,direct_message', async ( bot, message ) => { 36 | 37 | await bot.beginDialog( 'states_quiz' ); 38 | }); 39 | 40 | controller.commandHelp.push( { command: 'states', text: 'Conversation/thread example implementing a state quiz' } ); 41 | 42 | } -------------------------------------------------------------------------------- /features/webex_cards.js: -------------------------------------------------------------------------------- 1 | // 2 | // Demo interactive adaptive cards 3 | // 4 | module.exports = function(controller) { 5 | 6 | controller.hears( 'monitor', 'message,direct_message', async ( bot, message ) => { 7 | 8 | if (!controller.public_url) { 9 | await bot.reply( message, { 10 | text: 'Please configure the PUBLIC_URL setting to enable this sample feature' 11 | } ); 12 | return; 13 | } 14 | 15 | await bot.reply( message, { 16 | text: 'VM Monitor', 17 | attachments: [ 18 | { 19 | 'contentType': 'application/vnd.microsoft.card.adaptive', 20 | 'content': { 21 | 'type': 'AdaptiveCard', 22 | 'version': '1.0', 23 | 'body': [ 24 | { 25 | 'type': 'ColumnSet', 26 | 'columns': [ 27 | { 28 | 'type': 'Column', 29 | 'width': 'stretch', 30 | 'items': [ 31 | { 32 | 'type': 'TextBlock', 33 | 'text': 'VM Monitor', 34 | 'size': 'ExtraLarge', 35 | 'weight': 'Bolder', 36 | 'horizontalAlignment': 'Center' 37 | } 38 | ], 39 | 'verticalContentAlignment': 'Center' 40 | }, 41 | { 42 | 'type': 'Column', 43 | 'width': 'stretch', 44 | 'items': [ 45 | { 46 | 'type': 'Image', 47 | 'altText': '', 48 | 'url': `${controller.public_url}/www/monitor.png` 49 | } 50 | ] 51 | } 52 | ] 53 | }, 54 | { 55 | 'type': 'Input.ChoiceSet', 56 | 'placeholder': 'Placeholder text', 57 | 'choices': [ 58 | { 59 | 'title': 'CUCM Pub', 60 | 'value': '10.10.0.1' 61 | }, 62 | { 63 | 'title': 'CUCM Sub', 64 | 'value': '10.10.0.2' 65 | }, 66 | { 67 | 'title': 'IM&P', 68 | 'value': '10.10.0.3' 69 | } 70 | ], 71 | 'value': '10.10.0.1', 72 | 'id': 'vmlist' 73 | } 74 | ], 75 | '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json', 76 | 'actions': [ 77 | { 78 | 'type': 'Action.Submit', 79 | 'title': 'Submit' 80 | } 81 | ] 82 | } 83 | } 84 | ] 85 | }) 86 | }) 87 | 88 | controller.on( 'attachmentActions', async ( bot, message ) => { 89 | 90 | let hostName = message.value.vmlist; 91 | 92 | await bot.reply( message, { 93 | text: 'Stats', 94 | attachments: [ 95 | { 96 | 'contentType': 'application/vnd.microsoft.card.adaptive', 97 | 'content': { 98 | 'type': 'AdaptiveCard', 99 | 'version': '1.0', 100 | 'body': [ 101 | { 102 | 'type': 'ColumnSet', 103 | 'columns': [ 104 | { 105 | 'type': 'Column', 106 | 'width': 'stretch', 107 | 'items': [ 108 | { 109 | 'type': 'TextBlock', 110 | 'text': 'VM Monitor', 111 | 'size': 'ExtraLarge', 112 | 'weight': 'Bolder', 113 | 'horizontalAlignment': 'Center' 114 | }, 115 | { 116 | 'type': 'TextBlock', 117 | 'text': 'Data for Host:' 118 | }, 119 | { 120 | 'type': 'TextBlock', 121 | 'text': `${ hostName }`, 122 | 'weight': 'Bolder' 123 | } 124 | ], 125 | 'verticalContentAlignment': 'Center', 126 | 'horizontalAlignment': 'Center' 127 | }, 128 | { 129 | 'type': 'Column', 130 | 'width': 'stretch', 131 | 'items': [ 132 | { 133 | 'type': 'Image', 134 | 'altText': '', 135 | 'url': `${controller.public_url}/www/monitor.png` 136 | } 137 | ] 138 | } 139 | ] 140 | }, 141 | { 142 | 'type': 'Image', 143 | 'altText': '', 144 | 'url': `${controller.public_url}/www/stats.png` 145 | } 146 | ], 147 | '$schema': 'http://adaptivecards.io/schemas/adaptive-card.json' 148 | } 149 | } 150 | ] 151 | }) 152 | }) 153 | 154 | controller.commandHelp.push( { command: 'monitor', text: 'Demo interactive adaptive cards' } ); 155 | 156 | } 157 | -------------------------------------------------------------------------------- /features/webex_features.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. 4 | */ 5 | 6 | const { BotkitConversation } = require('botkit'); 7 | 8 | module.exports = function(controller) { 9 | 10 | const NEW_ROOM_DIALOG = 'new_room_dialog'; 11 | const dialog = new BotkitConversation(NEW_ROOM_DIALOG, controller); 12 | dialog.say('I created this room so we could continue our conversation in private...'); 13 | dialog.ask('How does that sound?', async (response, convo, bot) => { 14 | 15 | }, {key: 'how_it_sounds'}); 16 | dialog.say('Ah, {{vars.how_it_sounds}}, eh?'); 17 | dialog.say('I guess that is that.') 18 | 19 | controller.addDialog(dialog); 20 | 21 | controller.hears('delete','message,direct_message', async (bot, message) => { 22 | 23 | let reply = await bot.reply(message,'This message will be deleted in a few seconds.'); 24 | setTimeout(async () => { 25 | let res = await bot.deleteMessage(reply); 26 | }, 5000); 27 | 28 | }); 29 | 30 | 31 | controller.hears('create','message,direct_message', async (bot, message) => { 32 | 33 | // create a room 34 | let room = await bot.api.rooms.create({title: 'Botkit test room'}); 35 | 36 | // add user as member (bot is automatically added) 37 | let membership2 = await bot.api.memberships.create({ 38 | roomId: room.id, 39 | personId: message.user, 40 | }); 41 | 42 | await bot.startConversationInRoom(room.id, message.user); 43 | await bot.beginDialog(NEW_ROOM_DIALOG); 44 | 45 | }); 46 | 47 | controller.on('memberships.created', async (bot, message) => { 48 | console.log('membership created: ', message); 49 | }); 50 | 51 | controller.commandHelp.push( { command: 'create', text: 'Create a 1:1 space between the bot and the user' } ); 52 | controller.commandHelp.push( { command: 'delete', text: 'Delete a message dynamically' } ); 53 | 54 | } -------------------------------------------------------------------------------- /features/welcome.js: -------------------------------------------------------------------------------- 1 | // 2 | // Welcome message 3 | // sent as the bot is added to a Room 4 | // 5 | module.exports = function (controller) { 6 | 7 | controller.on( 'memberships.created', async ( bot, message ) => { 8 | 9 | // If the person being added to a space isn't the bot, exit 10 | if ( message.data.personId != controller.adapter.identity.id ) return; 11 | 12 | let markDown = `Hi, I am the **${ controller.adapter.identity.displayName }** bot! \n` 13 | markDown += 'Type `help` to learn more about my skills. '; 14 | 15 | if ( message.data.roomType == 'group' ) { 16 | 17 | markDown += `\n_Note that this is a "group" space.\n I will answer only if mentioned! \n` 18 | markDown += `For help, enter: ${ controller.checkAddMention( message.data.roomType, 'help' ) }_` 19 | } 20 | 21 | await bot.reply( message, { markdown : markDown} ); 22 | }); 23 | } -------------------------------------------------------------------------------- /features/z-fallback.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Fallback Command 4 | // 5 | module.exports = function (controller) { 6 | 7 | controller.on( 'message,direct_message', async ( bot, message ) => { 8 | 9 | let markDown = `Sorry, I did not understand. \nTry: ${ controller.checkAddMention( message.roomType, 'help' ) }`; 10 | 11 | await bot.reply( message, { markdown: markDown } ); 12 | }); 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "botbuilder-adapter-webex": "^1.0.10", 4 | "botbuilder-storage-mongodb": "^1.0.8", 5 | "botbuilder-storage-redis": "^1.1.2", 6 | "botkit": "^4.15.0", 7 | "dotenv": "^16.0.3", 8 | "redis": "^4.3.1", 9 | "uuid": "^9.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /www/monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/botkit-template/54807d4079aa9aad2817d2256406bbc7de9611ff/www/monitor.png -------------------------------------------------------------------------------- /www/stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiscoDevNet/botkit-template/54807d4079aa9aad2817d2256406bbc7de9611ff/www/stats.png --------------------------------------------------------------------------------