From 977051e9172d1e6aeab829e7460ed08efa81c6d5 Mon Sep 17 00:00:00 2001 From: Ouadie Lahdioui Date: Sun, 15 Jul 2018 03:29:05 +0200 Subject: [PATCH 1/5] initial version of botkit google hangouts bot --- examples/google_hangouts_bot.js | 17 +++ lib/Botkit.js | 2 + lib/GoogleHangoutsBot.js | 222 ++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 examples/google_hangouts_bot.js create mode 100644 lib/GoogleHangoutsBot.js diff --git a/examples/google_hangouts_bot.js b/examples/google_hangouts_bot.js new file mode 100644 index 000000000..63c9eef5e --- /dev/null +++ b/examples/google_hangouts_bot.js @@ -0,0 +1,17 @@ +var Botkit = require('../lib/Botkit.js'); +var controller = Botkit.googlehangoutsbot({ + endpoint: 'Axjn86rTGRQwisaYFyT0XZyiOCh7rZUPGx1A', + token: "efe_CIaKfFDdQlm_HBnQBkVeJUC_yNF3Uhs2lNeCdYs=", + debug: true, +}); +var bot = controller.spawn({}); + +controller.setupWebserver(3000, function(err, webserver) { + controller.createWebhookEndpoints(webserver, bot, function() { + console.log('ONLINE!'); + }); +}); + +controller.hears('hello', 'message_received', function(bot, message) { + console.log("2 >> " + message.text); +}); \ No newline at end of file diff --git a/lib/Botkit.js b/lib/Botkit.js index bf06c9ee9..6ae79db67 100755 --- a/lib/Botkit.js +++ b/lib/Botkit.js @@ -8,6 +8,7 @@ var SparkBot = require(__dirname + '/CiscoSparkbot.js'); var ConsoleBot = require(__dirname + '/ConsoleBot.js'); var JabberBot = require(__dirname + '/JabberBot.js'); var WebBot = require(__dirname + '/Web.js'); +var GoogleHangoutsBot = require(__dirname + '/GoogleHangoutsBot.js'); module.exports = { core: CoreBot, @@ -22,4 +23,5 @@ module.exports = { jabberbot: JabberBot, socketbot: WebBot, anywhere: WebBot, + googlehangoutsbot: GoogleHangoutsBot }; diff --git a/lib/GoogleHangoutsBot.js b/lib/GoogleHangoutsBot.js new file mode 100644 index 000000000..db88f4df1 --- /dev/null +++ b/lib/GoogleHangoutsBot.js @@ -0,0 +1,222 @@ +var Botkit = require(__dirname + '/CoreBot.js'); + +function GoogleHangoutsBot(configuration) { + + var google_hangouts_botkit = Botkit(configuration || {}); + + google_hangouts_botkit.defineBot(function (botkit, config) { + + var bot = { + type: 'googlehangouts', + botkit: botkit, + config: config || {}, + utterances: botkit.utterances, + }; + + bot.send = function (message, cb) { + + + //Add Access Token to outgoing request + message.access_token = configuration.access_token; + + if (facebook_botkit.config.require_appsecret_proof) { + message.appsecret_proof = appsecret_proof; + } + + request({ + method: 'POST', + json: true, + headers: { + 'content-type': 'application/json', + }, + body: message, + uri: 'https://' + api_host + '/' + api_version + '/me/messages' + }, + function (err, res, body) { + + if (err) { + botkit.debug('WEBHOOK ERROR', err); + return cb && cb(err); + } + + if (body.error) { + botkit.debug('API ERROR', body.error); + return cb && cb(body.error); + } + + botkit.debug('WEBHOOK SUCCESS', body); + cb && cb(null, body); + }); + }; + + bot.startTyping = function (src, cb) { + var msg = {}; + msg.channel = src.channel; + msg.sender_action = 'typing_on'; + bot.say(msg, cb); + }; + + bot.stopTyping = function (src, cb) { + var msg = {}; + msg.channel = src.channel; + msg.sender_action = 'typing_off'; + bot.say(msg, cb); + }; + + bot.replyWithTyping = function (src, resp, cb) { + var textLength; + + if (typeof (resp) == 'string') { + textLength = resp.length; + } else if (resp.text) { + textLength = resp.text.length; + } else { + textLength = 80; //default attachement text length + } + + var avgWPM = 85; + var avgCPM = avgWPM * 7; + + var typingLength = Math.min(Math.floor(textLength / (avgCPM / 60)) * 1000, 5000); + + bot.startTyping(src, function (err) { + if (err) console.error(err); + setTimeout(function () { + bot.reply(src, resp, cb); + }, typingLength); + }); + + }; + + bot.reply = function (src, resp, cb) { + var msg = {}; + + if (typeof (resp) == 'string') { + msg.text = resp; + } else { + msg = resp; + } + + msg.channel = src.channel; + msg.to = src.user; + + bot.say(msg, cb); + }; + + bot.findConversation = function (message, cb) { + botkit.debug('CUSTOM FIND CONVO', message.user, message.channel); + for (var t = 0; t < botkit.tasks.length; t++) { + for (var c = 0; c < botkit.tasks[t].convos.length; c++) { + if ( + botkit.tasks[t].convos[c].isActive() && + botkit.tasks[t].convos[c].source_message.user == message.user && + botkit.excludedEvents.indexOf(message.type) == -1 // this type of message should not be included + ) { + botkit.debug('FOUND EXISTING CONVO!'); + cb(botkit.tasks[t].convos[c]); + return; + } + } + } + + cb(); + }; + + bot.getInstanceInfo = function (cb) { + return facebook_botkit.getInstanceInfo(cb); + }; + + bot.getMessageUser = function (message, cb) { + return new Promise(function (resolve, reject) { + facebook_botkit.api.user_profile(message.user).then(function (identity) { + + // normalize this into what botkit wants to see + var profile = { + id: message.user, + username: identity.first_name + ' ' + identity.last_name, + first_name: identity.first_name, + last_name: identity.last_name, + full_name: identity.first_name + ' ' + identity.last_name, + email: identity.email || null, + gender: identity.gender, + locale: identity.locale, + picture: identity.picture, + timezone_offset: identity.timezone, + }; + + if (cb) { + cb(null, profile); + } + resolve(profile); + + }).catch(function (err) { + if (cb) { + cb(err); + } + reject(err); + }); + }); + + }; + + return bot; + }); + + google_hangouts_botkit.createWebhookEndpoints = function (webserver, bot, cb) { + + var endpoint = '/hangouts/' + google_hangouts_botkit.config.endpoint; + + google_hangouts_botkit.log( + '** Serving webhook endpoints for Google Hangout Platform at : ' + + 'http://' + google_hangouts_botkit.config.hostname + ':' + google_hangouts_botkit.config.port + endpoint + ); + + webserver.post(endpoint, function (req, res) { + res.status(200).send(); + google_hangouts_botkit.handleWebhookPayload(req, res, bot); + }); + + if (cb) { + cb(); + } + + return google_hangouts_botkit; + }; + + google_hangouts_botkit.handleWebhookPayload = function (req, res, bot) { + var payload = req.body; + if (google_hangouts_botkit.config.token && google_hangouts_botkit.config.token !== payload.token) { + google_hangouts_botkit.debug('Token verification failed, Ignoring message'); + } else { + google_hangouts_botkit.ingest(bot, payload, res); + } + }; + + google_hangouts_botkit.middleware.normalize.use(function handlePostback(bot, message, next) { + if (message.message) { + message.text = message.message.argumentText.trim(); + } + next(); + }); + + google_hangouts_botkit.middleware.categorize.use(function (bot, message, next) { + + if ('MESSAGE' === message.type) { + message.type = 'message_received'; + } + + if ('ADDED_TO_SPACE' === message.type) { + message.type = 'ROOM' === message.space.type ? 'room_join' : 'direct_message'; + } + + if ('REMOVED_FROM_SPACE' === message.type) { + message.type = 'ROOM' === message.space.type ? 'room_leave' : 'remove_conversation'; + } + + next(); + }); + + return google_hangouts_botkit; +}; + +module.exports = GoogleHangoutsBot; \ No newline at end of file From eb5fa3b42a0f38c9869dca4f7ae7e617a54f98f4 Mon Sep 17 00:00:00 2001 From: Ouadie Lahdioui Date: Wed, 18 Jul 2018 11:37:08 +0200 Subject: [PATCH 2/5] Finalize the Google Hangouts Bot --- lib/GoogleHangoutsBot.js | 200 ++++++++++++++++++--------------------- 1 file changed, 93 insertions(+), 107 deletions(-) diff --git a/lib/GoogleHangoutsBot.js b/lib/GoogleHangoutsBot.js index db88f4df1..eefa3c99e 100644 --- a/lib/GoogleHangoutsBot.js +++ b/lib/GoogleHangoutsBot.js @@ -1,13 +1,16 @@ var Botkit = require(__dirname + '/CoreBot.js'); +var {google} = require('googleapis'); function GoogleHangoutsBot(configuration) { + var api_version = 'v1'; + var google_hangouts_botkit = Botkit(configuration || {}); google_hangouts_botkit.defineBot(function (botkit, config) { var bot = { - type: 'googlehangouts', + type: 'googleHangouts', botkit: botkit, config: config || {}, utterances: botkit.utterances, @@ -15,91 +18,72 @@ function GoogleHangoutsBot(configuration) { bot.send = function (message, cb) { + const chat = google.chat({version: api_version, auth: bot.authClient}); - //Add Access Token to outgoing request - message.access_token = configuration.access_token; - - if (facebook_botkit.config.require_appsecret_proof) { - message.appsecret_proof = appsecret_proof; - } - - request({ - method: 'POST', - json: true, - headers: { - 'content-type': 'application/json', - }, - body: message, - uri: 'https://' + api_host + '/' + api_version + '/me/messages' - }, - function (err, res, body) { - - if (err) { - botkit.debug('WEBHOOK ERROR', err); - return cb && cb(err); + chat.spaces.messages.create(message) + .then(res => { + botkit.debug('Successfully sent message to Google Hangouts : ' + res.data.name); + if (cb) { + cb(null, res.data); } - - if (body.error) { - botkit.debug('API ERROR', body.error); - return cb && cb(body.error); + }) + .catch(err => { + botkit.debug('Error while sending message to Google Hangouts', err); + if (cb) { + cb(err); } - - botkit.debug('WEBHOOK SUCCESS', body); - cb && cb(null, body); }); }; - bot.startTyping = function (src, cb) { - var msg = {}; - msg.channel = src.channel; - msg.sender_action = 'typing_on'; - bot.say(msg, cb); - }; - - bot.stopTyping = function (src, cb) { - var msg = {}; - msg.channel = src.channel; - msg.sender_action = 'typing_off'; - bot.say(msg, cb); - }; + bot.reply = function (src, resp, cb) { - bot.replyWithTyping = function (src, resp, cb) { - var textLength; + var message_to_send = { + parent: src.space.name, + threadKey: undefined, + requestBody: {} + }; if (typeof (resp) == 'string') { - textLength = resp.length; + message_to_send.requestBody.text = resp; } else if (resp.text) { - textLength = resp.text.length; + message_to_send.requestBody.text = resp.text; } else { - textLength = 80; //default attachement text length + message_to_send.requestBody = resp.requestBody; } - var avgWPM = 85; - var avgCPM = avgWPM * 7; + message_to_send.requestBody.thread = { + name: src.message.thread.name + }; - var typingLength = Math.min(Math.floor(textLength / (avgCPM / 60)) * 1000, 5000); + bot.say(message_to_send, cb); - bot.startTyping(src, function (err) { - if (err) console.error(err); - setTimeout(function () { - bot.reply(src, resp, cb); - }, typingLength); - }); + }; + + bot.replyWithThreadKey = function (src, resp, cb) { + var msg = {}; + + msg.parent = src.space.name; + msg.threadKey = resp.threadKey; + msg.requestBody = resp.requestBody; + + bot.say(msg, cb); }; - bot.reply = function (src, resp, cb) { + bot.replyAsNewThread = function (src, resp, cb) { + var msg = {}; + msg.parent = src.space.name; + if (typeof (resp) == 'string') { - msg.text = resp; + msg.requestBody = { + text: resp + }; } else { - msg = resp; + msg.requestBody = resp; } - msg.channel = src.channel; - msg.to = src.user; - bot.say(msg, cb); }; @@ -107,11 +91,7 @@ function GoogleHangoutsBot(configuration) { botkit.debug('CUSTOM FIND CONVO', message.user, message.channel); for (var t = 0; t < botkit.tasks.length; t++) { for (var c = 0; c < botkit.tasks[t].convos.length; c++) { - if ( - botkit.tasks[t].convos[c].isActive() && - botkit.tasks[t].convos[c].source_message.user == message.user && - botkit.excludedEvents.indexOf(message.type) == -1 // this type of message should not be included - ) { + if (botkit.tasks[t].convos[c].isActive() && botkit.tasks[t].convos[c].source_message.user == message.user) { botkit.debug('FOUND EXISTING CONVO!'); cb(botkit.tasks[t].convos[c]); return; @@ -122,43 +102,6 @@ function GoogleHangoutsBot(configuration) { cb(); }; - bot.getInstanceInfo = function (cb) { - return facebook_botkit.getInstanceInfo(cb); - }; - - bot.getMessageUser = function (message, cb) { - return new Promise(function (resolve, reject) { - facebook_botkit.api.user_profile(message.user).then(function (identity) { - - // normalize this into what botkit wants to see - var profile = { - id: message.user, - username: identity.first_name + ' ' + identity.last_name, - first_name: identity.first_name, - last_name: identity.last_name, - full_name: identity.first_name + ' ' + identity.last_name, - email: identity.email || null, - gender: identity.gender, - locale: identity.locale, - picture: identity.picture, - timezone_offset: identity.timezone, - }; - - if (cb) { - cb(null, profile); - } - resolve(profile); - - }).catch(function (err) { - if (cb) { - cb(err); - } - reject(err); - }); - }); - - }; - return bot; }); @@ -172,7 +115,6 @@ function GoogleHangoutsBot(configuration) { ); webserver.post(endpoint, function (req, res) { - res.status(200).send(); google_hangouts_botkit.handleWebhookPayload(req, res, bot); }); @@ -184,23 +126,64 @@ function GoogleHangoutsBot(configuration) { }; google_hangouts_botkit.handleWebhookPayload = function (req, res, bot) { + var payload = req.body; + if (google_hangouts_botkit.config.token && google_hangouts_botkit.config.token !== payload.token) { + res.status(401); google_hangouts_botkit.debug('Token verification failed, Ignoring message'); } else { + res.status(200); google_hangouts_botkit.ingest(bot, payload, res); } + + res.send(); }; + google_hangouts_botkit.middleware.format.use(function (bot, message, platform_message, next) { + + platform_message.parent = message.parent; + platform_message.threadKey = message.threadKey; + platform_message.requestBody = message.requestBody; + + next(); + + }); + + google_hangouts_botkit.middleware.spawn.use(function (worker, next) { + + let params = { + scopes: 'https://www.googleapis.com/auth/chat.bot' + }; + + google + .auth + .getClient(params) + .then(client => { + worker.authClient = client; + }) + .catch(err => { + console.error('Could not get google auth client !'); + throw new Error(err); + }); + + next(); + + }); + google_hangouts_botkit.middleware.normalize.use(function handlePostback(bot, message, next) { + message.user = message.user.name; if (message.message) { + message.channel = message.message.thread.name; message.text = message.message.argumentText.trim(); + } else { + message.channel = message.space.name; } next(); }); google_hangouts_botkit.middleware.categorize.use(function (bot, message, next) { - + if ('MESSAGE' === message.type) { message.type = 'message_received'; } @@ -214,9 +197,12 @@ function GoogleHangoutsBot(configuration) { } next(); + }); + google_hangouts_botkit.startTicking(); + return google_hangouts_botkit; }; -module.exports = GoogleHangoutsBot; \ No newline at end of file +module.exports = GoogleHangoutsBot; From 78add23ba2e7e4d1bd9dd4bf8f4c51e9f0f9427c Mon Sep 17 00:00:00 2001 From: Ouadie Lahdioui Date: Wed, 18 Jul 2018 12:12:03 +0200 Subject: [PATCH 3/5] Add google hangouts chat doc link to the readme file --- docs/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/readme.md b/docs/readme.md index 89db59eb8..c5c59ffd6 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -16,6 +16,7 @@ All documentation for Botkit now lives at our official documentation site, [http * [Twilio SMS](https://botkit.ai/docs/readme-twiliosms.html) * [Twilio IPM](https://botkit.ai/docs/readme-twilioipm.html) * [Microsoft Bot Framework](https://botkit.ai/docs/readme-botframework.html) + * [Google Hangouts Chat](https://botkit.ai/docs/google-hangouts-chat.html) * [Extending Botkit with Plugins and Middleware](https://botkit.ai/docs/middleware.html) * [Message Pipeline](https://botkit.ai/docs/readme-pipeline.html) * [List of current plugins](https://botkit.ai/docs/readme-middlewares.html) From df842af1a223f038cc0f05380ccde04192dfd96c Mon Sep 17 00:00:00 2001 From: Ouadie Lahdioui Date: Wed, 18 Jul 2018 11:58:01 +0200 Subject: [PATCH 4/5] Add google hangouts bot example --- examples/google_hangouts_bot.js | 88 ++++++++++++++++++++++++++++++--- package.json | 1 + 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/examples/google_hangouts_bot.js b/examples/google_hangouts_bot.js index 63c9eef5e..c6c3152e6 100644 --- a/examples/google_hangouts_bot.js +++ b/examples/google_hangouts_bot.js @@ -1,17 +1,91 @@ var Botkit = require('../lib/Botkit.js'); + var controller = Botkit.googlehangoutsbot({ endpoint: 'Axjn86rTGRQwisaYFyT0XZyiOCh7rZUPGx1A', - token: "efe_CIaKfFDdQlm_HBnQBkVeJUC_yNF3Uhs2lNeCdYs=", + token: "YOUR_TOKEN", debug: true, }); + var bot = controller.spawn({}); -controller.setupWebserver(3000, function(err, webserver) { - controller.createWebhookEndpoints(webserver, bot, function() { - console.log('ONLINE!'); +controller.setupWebserver(3000, function (err, webserver) { + controller.createWebhookEndpoints(webserver, bot, function () { + console.log(`🚀 Congratulation, the web server is online!`); + }); +}); + +controller.on('message_received', function (bot, message) { + bot.reply(message, `You said '${message.text}'`); +}); + +controller.hears('new thread', 'message_received', function (bot, message) { + bot.replyAsNewThread(message, `Hello ! this is a new thread`); +}); + +controller.hears('thread key', 'message_received', function (bot, message) { + bot.replyWithThreadKey(message, { + threadKey : "YOUR_THREAD_KEY", + requestBody : { + text : `Hi ! this message inside the same thread` + } }); }); -controller.hears('hello', 'message_received', function(bot, message) { - console.log("2 >> " + message.text); -}); \ No newline at end of file +controller.hears('convo', 'message_received', function (bot, message) { + + bot.startConversation(message, function(err, convo) { + + convo.ask('You want to know more about Botkit ?', [ + { + pattern: bot.utterances.yes, + callback: function(response, convo) { + convo.say('Take a look here https://botkit.ai/docs/'); + convo.next(); + } + }, + { + pattern: bot.utterances.no, + default: true, + callback: function(response, convo) { + convo.say('No problem'); + convo.next(); + } + } + ]); + }); + +}); + +controller.hears('cards', 'message_received', function (bot, message) { + bot.reply(message, { + requestBody: { + cards: [ + { + "sections": [ + { + "widgets": [ + { + "image": { "imageUrl": "https://image.slidesharecdn.com/botkitsignal-160526164159/95/build-a-bot-with-botkit-1-638.jpg?cb=1464280993" } + }, + { + "buttons": [ + { + "textButton": { + "text": "Get Started", + "onClick": { + "openLink": { + "url": "https://botkit.ai/docs/" + } + } + } + } + ] + } + ] + } + ] + } + ] + } + }); +}); diff --git a/package.json b/package.json index 627087b2b..13a828e10 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "debug": "^3.1.0", "email-addresses": "^3.0.1", "express": "^4.15.2", + "googleapis": "^32.0.0", "https-proxy-agent": "^2.2.1", "jfs": "^0.3.0", "localtunnel": "^1.8.2", From 45fd079e23a45f8b70246343b434d5ceaf04416a Mon Sep 17 00:00:00 2001 From: Ouadie Lahdioui Date: Wed, 1 Aug 2018 00:42:40 +0200 Subject: [PATCH 5/5] Adding Google Hangouts to CLI --- bin/cli.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/cli.js b/bin/cli.js index e42ba3bc4..7a0db9ef8 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -7,7 +7,7 @@ var fs = require('fs'); var botkit = Vorpal() -var platforms = ['web', 'teams', 'ciscospark', 'slack', 'facebook']; +var platforms = ['web', 'teams', 'ciscospark', 'slack', 'facebook', 'googlehangouts']; var platform_src = [{ platform: 'web', artifact: 'https://github.com/howdyai/botkit-starter-web.git', @@ -38,6 +38,11 @@ var platform_src = [{ platform: 'facebook', artifact: 'https://github.com/howdyai/botkit-starter-facebook.git', directory: 'botkit-starter-facebook' + }, + { + platform: 'googlehangouts', + artifact: 'git@github.com:howdyai/botkit-starter-googlehangouts.git', + directory: 'botkit-starter-googlehangouts' } ];