Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Google Hangouts Chat #125

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/bot-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const groupmeSetup = require('./groupme/setup');
const lineSetup = require('./line/setup');
const viberSetup = require('./viber/setup');
const alexaSetup = require('./alexa/setup');
const googleHangoutsSetup = require('./google-hangouts-chat/setup');
const fbTemplate = require('./facebook/format-message');
const slackTemplate = require('./slack/format-message');
const slackDialog = require('./slack/format-dialog');
Expand Down Expand Up @@ -68,6 +69,9 @@ module.exports = function botBuilder(messageHandler, options, optionalLogError)
if (isEnabled('alexa')) {
alexaSetup(api, messageHandlerPromise, logError);
}
if (isEnabled('google-hangouts-chat')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use just hangouts because it's shorter but still describes the platform uniquely.

googleHangoutsSetup(api, messageHandlerPromise, logError);
}

return api;
};
Expand Down
33 changes: 33 additions & 0 deletions lib/google-hangouts-chat/parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use strict';

module.exports = function googleHangoutsParse(messageObject) {
if (!messageObject || !messageObject.type) return;

const message = messageObject.message;

const text = message && message.text ? message.text : '';

let messageType = 'unknown';
switch (messageObject.type) {
case 'MESSAGE':
messageType = 'message';
break;
case 'ADDED_TO_SPACE':
messageType = 'add-command';
break;
case 'REMOVED_FROM_SPACE':
messageType = 'remove-command';
break;
}

const sender = messageType === 'message' ?
message && message.sender && message.sender.name :
(messageObject.user && messageObject.user.name || 'no sender');

return {
sender: sender,
text: text,
originalRequest: messageObject,
type: `google-hangouts-chat-${messageType}`
};
};
11 changes: 11 additions & 0 deletions lib/google-hangouts-chat/reply.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = function googleHangoutsReply(botResponse) {
if (typeof botResponse === 'string') {
return {
text: botResponse
};
}

return botResponse;
};
44 changes: 44 additions & 0 deletions lib/google-hangouts-chat/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const prompt = require('souffleur');
const googleHangoutsParse = require('./parse');
const googleHangoutsReply = require('./reply');
const color = require('../console-colors');
const envUtils = require('../utils/env-utils');

module.exports = function googleHangoutsSetup(api, bot, logError, optionalParser, optionalResponder) {
let parser = optionalParser || googleHangoutsParse;
let responder = optionalResponder || googleHangoutsReply;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use const instead of let for both parser and responder, as they are not re-assigned. I guess should update that for other bots too.


api.post('/google-hangouts-chat', request => {
return bot(parser(request.body), request)
.then(botReply => responder(botReply, envUtils.decode(request.env.googleHangoutsAppName)))
.catch(logError);
});

api.addPostDeployStep('google-hangouts-chat', (options, lambdaDetails, utils) => {
return Promise.resolve().then(() => {
if (options['configure-google-hangouts-chat-bot']) {
console.log(`\n\n${color.green}Google Hangouts Chat bot setup${color.reset}\n`);
console.log(`\nConfigure your Google Hangouts Chat bot endpoint to HTTPS and set this URL:.\n`);
console.log(`\n${color.cyan}${lambdaDetails.apiUrl}/google-hangouts-chat${color.reset}\n`);

return prompt(['Google Hangouts Chat bot name'])
.then(results => {
const deployment = {
restApiId: lambdaDetails.apiId,
stageName: lambdaDetails.alias,
variables: {
googleHangoutsAppName: envUtils.encode(results['Google Hangouts Chat bot name'])
}
};

console.log(`\n`);

return utils.apiGatewayPromise.createDeploymentPromise(deployment);
});
}
})
.then(() => `${lambdaDetails.apiUrl}/google-hangouts-chat`);
});
};
58 changes: 58 additions & 0 deletions spec/google-hangouts-chat/hangouts-chat-parse-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*global describe, it, expect, require */
'use strict';

const parse = require('../../lib/google-hangouts-chat/parse');

describe('Google Hangout Chat parse', () => {
it('should return nothing if the format is invalid', () => {
expect(parse('string')).toBeUndefined();
expect(parse()).toBeUndefined();
expect(parse(false)).toBeUndefined();
expect(parse(123)).toBeUndefined();
expect(parse({})).toBeUndefined();
expect(parse([1, 2, 3])).toBeUndefined();
});
it('should return undefined if the session user is missing', () => {
expect(parse({message: {}})).toBeUndefined();
});
it('should return original request with an empty text if the intent is missing', () => {
let msg = {message: {foo: 'bar'}, type: 'ADDED_TO_SPACE'};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msg is never re-assigned, use const

expect(parse(msg)).toEqual({ sender: 'no sender', text: '', originalRequest: msg, type: 'google-hangouts-chat-add-command'});
});
it('should return original request with an empty text if the intent name is missing', () => {
let msg = {message: {foo: 'bar'}, type: 'REMOVED_FROM_SPACE'};
expect(parse(msg)).toEqual({ sender: 'no sender', text: '', originalRequest: msg, type: 'google-hangouts-chat-remove-command'});
});
it('should return a parsed object with proper sender and text when the intent name and session user are present', () => {
const msg = {
'type': 'MESSAGE',
'eventTime': '2017-03-02T19:02:59.910959Z',
'space': {
'name': 'spaces/AAAAAAAAAAA',
'displayName': 'Ramdom Discussion Room',
'type': 'ROOM'
},
'message': {
'name': 'spaces/AAAAAAAAAAA/messages/CCCCCCCCCCC',
'sender': {
'name': 'users/12345678901234567890',
'displayName': 'John Doe',
'avatarUrl': 'https://lh3.googleusercontent.com/.../photo.jpg',
'email': '[email protected]'
},
'createTime': '2017-03-02T19:02:59.910959Z',
'text': 'Hello World',
'thread': {
'name': 'spaces/AAAAAAAAAAA/threads/BBBBBBBBBBB'
}
}
};

expect(parse(msg)).toEqual({
sender: 'users/12345678901234567890',
text: 'Hello World',
originalRequest: msg,
type: 'google-hangouts-chat-message'
});
});
});
19 changes: 19 additions & 0 deletions spec/google-hangouts-chat/hangouts-chat-reply-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*global describe, it, expect, require */
'use strict';
const reply = require('../../lib/google-hangouts-chat/reply');

describe('Google Hangouts Chat Reply', () => {

it('just returns the bot response when its not a string', () => {
expect(reply()).toEqual(undefined);
expect(reply(undefined, 'Google Hangouts Chat Bot')).toEqual(undefined);
expect(reply({ hello: 'Google Hangouts Chat Bot'}, 'Google Hangouts Chat Bot')).toEqual({ hello: 'Google Hangouts Chat Bot'});
});

it('just returns the proper Google Hangouts Chat response when its not a string', () => {
expect(reply('Google Hangouts Chat Bot', 'Google Hangouts Chat Bot'))
.toEqual({
text: 'Google Hangouts Chat Bot'
});
});
});
141 changes: 141 additions & 0 deletions spec/google-hangouts-chat/hangouts-chat-setup-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*global require, describe, it, expect, beforeEach, jasmine*/
'use strict';
const underTest = require('../../lib/google-hangouts-chat/setup'),
utils = require('../../lib/utils/env-utils');
describe('Google Hangouts Chat setup', () => {
let api, bot, logError, parser, responder, botPromise, botResolve, botReject;
beforeEach(() => {
api = jasmine.createSpyObj('api', ['get', 'post', 'addPostDeployStep']);
botPromise = new Promise((resolve, reject) => {
botResolve = resolve;
botReject = reject;
});
bot = jasmine.createSpy().and.returnValue(botPromise);
parser = jasmine.createSpy();
logError = jasmine.createSpy();
responder = jasmine.createSpy();
underTest(api, bot, logError, parser, responder);
});
describe('message processor', () => {
const singleMessageTemplate = {
'type': 'MESSAGE',
'eventTime': '2017-03-02T19:02:59.910959Z',
'space': {
'name': 'spaces/AAAAAAAAAAA',
'displayName': 'Ramdom Discussion Room',
'type': 'ROOM'
},
'message': {
'name': 'spaces/AAAAAAAAAAA/messages/CCCCCCCCCCC',
'sender': {
'name': 'users/12345678901234567890',
'displayName': 'John Doe',
'avatarUrl': 'https://lh3.googleusercontent.com/.../photo.jpg',
'email': '[email protected]'
},
'createTime': '2017-03-02T19:02:59.910959Z',
'text': 'Hello World',
'thread': {
'name': 'spaces/AAAAAAAAAAA/threads/BBBBBBBBBBB'
}
}
};
it('wires the POST request for Google Hangouts Chat to the message processor', () => {
expect(api.post.calls.count()).toEqual(1);
expect(api.post).toHaveBeenCalledWith('/google-hangouts-chat', jasmine.any(Function));
});
describe('processing a single message', () => {
let handler;
beforeEach(() => {
handler = api.post.calls.argsFor(0)[1];
});
it('breaks down the message and puts it into the parser', () => {
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: 'Google Hangouts Chat Bot'}});
expect(parser).toHaveBeenCalledWith(singleMessageTemplate);
});
it('passes the parsed value to the bot if a message can be parsed', (done) => {
parser.and.returnValue('MSG1');
handler({body: singleMessageTemplate, env: {}});
Promise.resolve().then(() => {
expect(bot).toHaveBeenCalledWith('MSG1', { body: singleMessageTemplate, env: {} });
}).then(done, done.fail);
});
it('responds when the bot resolves', (done) => {
parser.and.returnValue({sender: 'user1', text: 'MSG1', type: 'google-hangouts-chat-message'});
botResolve('Hello Google Hangouts');
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: utils.encode('Google Hangouts Bot')}}).then(() => {
expect(responder).toHaveBeenCalledWith('Hello Google Hangouts', 'Google Hangouts Bot');
}).then(done, done.fail);
});
it('can work with bot responses as strings', (done) => {
botResolve('Hello Google Hangouts');
parser.and.returnValue({sender: 'user1', text: 'Hello'});
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: utils.encode('Google Hangouts Bot')}}).then(() => {
expect(responder).toHaveBeenCalledWith('Hello Google Hangouts', 'Google Hangouts Bot');
}).then(done, done.fail);

});
it('logs error when the bot rejects without responding', (done) => {
parser.and.returnValue('MSG1');

handler({body: singleMessageTemplate, env: {googleHangoutsAppName: 'Google Hangouts Bot'}}).then(() => {
expect(responder).not.toHaveBeenCalled();
expect(logError).toHaveBeenCalledWith('No No');
}).then(done, done.fail);

botReject('No No');
});
it('logs the error when the responder throws an error', (done) => {
parser.and.returnValue('MSG1');
responder.and.throwError('XXX');
botResolve('Yes');
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: 'Google Hangouts Bot'}}).then(() => {
expect(logError).toHaveBeenCalledWith(jasmine.any(Error));
}).then(done, done.fail);
});
describe('working with promises in responders', () => {
let responderResolve, responderReject, responderPromise, hasResolved;
beforeEach(() => {
responderPromise = new Promise((resolve, reject) => {
responderResolve = resolve;
responderReject = reject;
});
responder.and.returnValue(responderPromise);

parser.and.returnValue('MSG1');
});
it('waits for the responders to resolve before completing the request', (done) => {
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: 'Google Hangouts Bot'}}).then(() => {
hasResolved = true;
});

botPromise.then(() => {
expect(hasResolved).toBeFalsy();
}).then(done, done.fail);

botResolve('YES');
});
it('resolves when the responder resolves', (done) => {
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: 'Google Hangouts Bot'}}).then((message) => {
expect(message).toEqual('As Promised!');
}).then(done, done.fail);

botPromise.then(() => {
responderResolve('As Promised!');
});
botResolve('YES');
});
it('logs error when the responder rejects', (done) => {
handler({body: singleMessageTemplate, env: {googleHangoutsAppName: 'Google Hangouts Bot'}}).then(() => {
expect(logError).toHaveBeenCalledWith('Bomb!');
}).then(done, done.fail);

botPromise.then(() => {
responderReject('Bomb!');
});
botResolve('YES');
});
});
});
});
});