From 49b91c0a36c91a37e94e2496097e8628f2b8ba24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Markovi=C4=87?= Date: Mon, 23 Apr 2018 17:28:42 +0200 Subject: [PATCH] Improve chat flow with answers --- bot/facebook/conversation.js | 6 -- bot/facebook/index.js | 4 +- bot/facebook/intents/common/index.js | 3 + bot/facebook/intents/debug.js | 24 +++++--- bot/facebook/intents/default.js | 36 +++++++---- bot/facebook/intents/drink.js | 89 +++++++++++++++++++++++++--- bot/facebook/intents/index.js | 44 ++++++++++---- bot/facebook/intents/start.js | 64 +++++++++++++++++--- package.json | 2 + yarn.lock | 8 +++ 10 files changed, 226 insertions(+), 54 deletions(-) delete mode 100644 bot/facebook/conversation.js diff --git a/bot/facebook/conversation.js b/bot/facebook/conversation.js deleted file mode 100644 index 03347be..0000000 --- a/bot/facebook/conversation.js +++ /dev/null @@ -1,6 +0,0 @@ -const intents = require('./intents') - -module.exports = async (message, recipient) => { - const run = intents.get(message) - run(message, recipient) -} diff --git a/bot/facebook/index.js b/bot/facebook/index.js index d4c48e4..6094328 100644 --- a/bot/facebook/index.js +++ b/bot/facebook/index.js @@ -7,7 +7,7 @@ FB.setAccessToken(process.env.FB_ACCESS_TOKEN) const fbParser = require('claudia-bot-builder/lib/facebook/parse') const fbValidate = require('claudia-bot-builder/lib/facebook/validate-integrity') -const conversation = require('./conversation') +const {run} = require('./intents') /** * In-Memory cache for FB graph results @@ -63,7 +63,7 @@ const respond = async (req, res) => { recipients[message.sender] = recipient try { - await conversation(message, recipient) + await run(message, recipient) } catch (err) { console.error(err) res.end(err.message) diff --git a/bot/facebook/intents/common/index.js b/bot/facebook/intents/common/index.js index 5dfc5b5..cd622c6 100644 --- a/bot/facebook/intents/common/index.js +++ b/bot/facebook/intents/common/index.js @@ -3,6 +3,9 @@ const fbReply = require('claudia-bot-builder/lib/facebook/reply') const send = (recipientId, message) => fbReply(recipientId, message, process.env.FB_ACCESS_TOKEN) +const noop = () => {} + module.exports = { send, + noop, } diff --git a/bot/facebook/intents/debug.js b/bot/facebook/intents/debug.js index 7b3708c..2c6d840 100644 --- a/bot/facebook/intents/debug.js +++ b/bot/facebook/intents/debug.js @@ -1,14 +1,20 @@ +const flatten = require('flatten') const fbTemplate = require('claudia-bot-builder/lib/facebook/format-message') const {send} = require('./common') -const run = (msg, recipient) => { - const reply = new fbTemplate.Text(` - This is a \`Text\` message - `).get() - send(recipient.id, reply) +const INTENT_DEBUG_START = 'intent_debug_start' + +const run = (msg, recipient, data) => { + const text = flatten(data) + .map(d => JSON.stringify(d, null, 2)) + .join(', ') + send(recipient.id, new fbTemplate.Text(`DEBUG: \`${text}\``).get()) } -module.exports = { - keywords: ['debug'], - run, -} +module.exports = [ + { + id: 'debug', + keywords: [INTENT_DEBUG_START, 'debug'], + run, + }, +] diff --git a/bot/facebook/intents/default.js b/bot/facebook/intents/default.js index cae24b7..41f911f 100644 --- a/bot/facebook/intents/default.js +++ b/bot/facebook/intents/default.js @@ -1,16 +1,32 @@ +const huh = require('huh') const fbTemplate = require('claudia-bot-builder/lib/facebook/format-message') const {send} = require('./common') +const uncapitalize = str => str.charAt(0).toLowerCase() + str.slice(1) + const run = (msg, recipient) => { - const reply = new fbTemplate.Text( - `Sorry, ${ - recipient.first_name - }. I am a young Bot and still learning. Type "Start" to show the start over.` - ).get() - send(recipient.id, reply) + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Text( + `Sorry, ${ + recipient.first_name + }. I didn't understand that, probably because of ${uncapitalize( + huh.get() + )}` + ).get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(1000).get(), + new fbTemplate.Text( + `I'll improve in time, don't worry. Meanwhile, you can type 'start' to start the show over.` + ).get(), + ]) } -module.exports = { - keywords: [], - run, -} +module.exports = [ + { + id: 'default', + keywords: [], + run, + }, +] diff --git a/bot/facebook/intents/drink.js b/bot/facebook/intents/drink.js index 3279df9..ba69701 100644 --- a/bot/facebook/intents/drink.js +++ b/bot/facebook/intents/drink.js @@ -1,17 +1,90 @@ const fbTemplate = require('claudia-bot-builder/lib/facebook/format-message') const {send} = require('./common') +const INTENT_DRINK_START = 'intent_drink_start' +const INTENT_DRINK_YES = 'intent_drink_yes' +const INTENT_DRINK_NO = 'intent_drink_no' +const INTENT_DRINK_FAVORITE = 'intent_drink_favorite' + const run = (msg, recipient) => { - const reply = new fbTemplate.Text(` + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(500).get(), + new fbTemplate.Text(` So, you'd like to drink? `) - .addQuickReply('YES!', 'YES') - .addQuickReply('No, not particulary', 'NO') - .get() - send(recipient.id, reply) + .addQuickReply('YES!', INTENT_DRINK_YES) + .addQuickReply('No, not particulary', INTENT_DRINK_NO) + .get(), + ]) } -module.exports = { - keywords: ['drink', 'dring', 'water', 'gimmme'], - run, +const runYes = (msg, recipient) => { + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(500).get(), + new fbTemplate.Text(` + YEAH, me too. + `).get(), + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(1000).get(), + new fbTemplate.Text(` + What's your favorite drink? + `) + .addQuickReply('Absinthe', INTENT_DRINK_FAVORITE) + .addQuickReply('Beer', INTENT_DRINK_FAVORITE) + .addQuickReply('Brandy', INTENT_DRINK_FAVORITE) + .addQuickReply('Cachaça', INTENT_DRINK_FAVORITE) + .addQuickReply('Gin', INTENT_DRINK_FAVORITE) + .addQuickReply('Ouzo', INTENT_DRINK_FAVORITE) + .addQuickReply('Rum', INTENT_DRINK_FAVORITE) + .addQuickReply('Sake', INTENT_DRINK_FAVORITE) + .get(), + ]) } + +const runFavorite = (msg, recipient) => { + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(500).get(), + new fbTemplate.Text( + `Hmmm... I don't like ${ + msg.originalRequest.message.text + }, but I like water!` + ).get(), + ]) +} + +const runNo = (msg, recipient) => { + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(500).get(), + new fbTemplate.Text(` + That's a shame, but do drink water! + `).get(), + ]) +} + +module.exports = [ + { + keywords: [INTENT_DRINK_START, 'drink', 'dring', 'water', 'gimmme'], + run, + }, + { + keywords: [INTENT_DRINK_YES], + run: runYes, + }, + { + keywords: [INTENT_DRINK_NO], + run: runNo, + }, + { + keywords: [INTENT_DRINK_FAVORITE], + run: runFavorite, + }, +] diff --git a/bot/facebook/intents/index.js b/bot/facebook/intents/index.js index 6b5fdf9..bb07b1c 100644 --- a/bot/facebook/intents/index.js +++ b/bot/facebook/intents/index.js @@ -1,20 +1,40 @@ +const flatten = require('flatten') + +const load = intents => flatten(intents.map(name => require(`./${name}`))) + +const intents = load(['default', 'start', 'drink', 'debug']) + const normalize = str => str.toLowerCase() -const intents = { - default: require('./default'), - start: require('./start'), - drink: require('./drink'), - debug: require('./debug'), -} +const matchIntent = str => + intents + .map(i => (i.keywords.indexOf(normalize(str)) > -1 ? i.run : false)) + .find(f => f !== false) const get = msg => { - const text = normalize(msg.text) - const intent = Object.keys(intents).find( - k => intents[k].keywords.indexOf(text) !== -1 - ) - return intent ? intents[intent].run : intents.default.run + const defaultIntent = intents.find(i => i.id === 'default').run + let intent = matchIntent(msg.text) + + if (!intent) { + if (msg.quick_reply) { + intent = matchIntent(msg.quick_reply.payload) + } + } + + return intent || defaultIntent +} + +const run = (msg, recipient) => { + const debugIntent = intents.find(i => i.id === 'debug').run + const intent = get(msg) + + if (intent === debugIntent) { + return debugIntent(msg, recipient, intents) + } + + return intent(msg, recipient) } module.exports = { - get, + run, } diff --git a/bot/facebook/intents/start.js b/bot/facebook/intents/start.js index 0aaabb8..28248e9 100644 --- a/bot/facebook/intents/start.js +++ b/bot/facebook/intents/start.js @@ -1,14 +1,64 @@ const fbTemplate = require('claudia-bot-builder/lib/facebook/format-message') -const {send} = require('./common') +const {send, noop} = require('./common') + +const INTENT_START_START = 'intent_start_start' +const INTENT_START_ABOUT = 'intent_start_about' +const INTENT_START_CHANGE_ALERTS = 'intent_change_alert' const run = (msg, recipient) => { - const reply = new fbTemplate.Text(` + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(500).get(), + new fbTemplate.Text(` This is your menu. You can reach it by writing Menu, or Help, or Start - `).get() - send(recipient.id, reply) + `) + .addQuickReply('About vodopija-bot', INTENT_START_ABOUT) + .addQuickReply('Change alerts', INTENT_START_CHANGE_ALERTS) + .get(), + ]) } -module.exports = { - keywords: ['start', 'menu', 'help'], - run, +const runAbout = (msg, recipient) => { + send(recipient.id, [ + new fbTemplate.ChatAction('mark_seen').get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Text(`Thanks for asking 🙂`).get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(500).get(), + new fbTemplate.Text( + `vodolija-bot was created by Marko Marković as a test for SPARTANS AI LTD.` + ).get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(1000).get(), + new fbTemplate.Text( + `Its goal is to help you drink more water for a healthier life.` + ).get(), + new fbTemplate.ChatAction('typing_on').get(), + new fbTemplate.Pause(1000).get(), + new fbTemplate.Text(` +It features: +☑ Daily water reminders +☑ Personalized AI recommendations +☑ Number of cups of water drank this week +☑ Tips about water drinking +`) + .addQuickReply('Back', INTENT_START_START) + .get(), + ]) } + +module.exports = [ + { + keywords: [INTENT_START_START, 'start', 'menu', 'help'], + run, + }, + { + keywords: [INTENT_START_ABOUT], + run: runAbout, + }, + { + keywords: [INTENT_START_CHANGE_ALERTS], + run: noop, + }, +] diff --git a/package.json b/package.json index 87aa6b0..5b268e2 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ }, "dependencies": { "claudia-bot-builder": "4.0.0", + "flatten": "1.0.2", + "huh": "2.0.1", "micro": "9.1.4", "microrouter": "3.1.2" }, diff --git a/yarn.lock b/yarn.lock index 37e9165..5dcb7df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1127,6 +1127,10 @@ flat-cache@^1.2.1: graceful-fs "^4.1.2" write "^0.2.1" +flatten@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -1384,6 +1388,10 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +huh@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/huh/-/huh-2.0.1.tgz#08d5836a47e3f301ad50899d4788721b0ac15486" + iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"