How to develop a serverless chatbot (for Hangouts Chat) — Send messages to users

Mike
4 min readDec 26, 2018

--

Visit http://www.mikenikles.com for my latest blog posts.

Introduction

This post is part of a series where you can learn how to develop, monitor (and later debug and profile) a serverless chatbot (for Hangouts Chat). Please refer to the other posts linked below for context.

This post focuses on the highlighted part of the architecture

Send a message to a user (Steps A to D)

Step A — Create the reminder-bot-messages-out Pub/Sub topic and the reminder-bot-sender Cloud Function (PR #15)

This is very similar to step 3 & 4 earlier, let’s get right to it:

  1. Create a new package for the reminder-bot-sender service: npx lerna create reminder-bot-sender. Keep all default values.
  2. Replace the code of packages/reminder-bot-sender/lib/reminder-bot-sender.js with the following:
// packages/reminder-bot-sender/lib/reminder-bot-sender.jsexports.reminderBotSender = async (event, context) => {
console.log(`Input received:
Event: ${JSON.stringify(event)}
Context: ${JSON.stringify(context)}`)
}

Fabulous! Now let’s create a deployment script so we can deploy & test what we’ve just developed. We do this in the new service’s package.json by adding a new deploy script:

// packages/reminder-bot-sender/package.json{
...
"scripts": {
"deploy": "gcloud functions deploy reminderBotSender --runtime nodejs8 --trigger-topic reminder-bot-messages-out",
...
}
}

For consistency, let’s update the root-level package.json by adding a deploy script for this new service.

// package.json{
...
"scripts": {
"deploy:reminder-bot-receiver": "...",
"deploy:reminder-bot-sender": "lerna run deploy --stream --scope=\"reminder-bot-sender\"",
...
}
}

Validation: Well then, the moment of truth. Let’s deploy this with npm run deploy:reminder-bot-sender and look at the logs in real-time, while sending a message to the Reminder Bot from Hangouts Chat.

What a day! The `reminder-bot-sender` function received an event.

Notice the message value? It contains the time 5 minutes from when the reminder was created.

Step B — Send the message to the Hangouts Chat API (PR #16)

To communicate with the Hangouts Chat API, the googleapis NPM package contains a few neat helper functions. We can add it to the reminder-bot-sender service with npx lerna add googleapis --scope=reminder-bot-sender.

Next, it’s good practice to encapsulate the code that deals with the Hangouts Chat API. We do this in its own hangouts-chat-api.js module:

// packages/reminder-bot-sender/lib/hangouts-chat-api.jsconst { google } = require('googleapis')
const chat = google.chat({
version: 'v1'
})
const sendResponse = async (chatResponse) => {
console.log('Obtaining auth client for the Hangouts Chat API.')
const auth = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/chat.bot']
});
console.log('Sending message to the Hangouts Chat API.')
const payload = {
parent: chatResponse.spaceName,
requestBody: {
text: chatResponse.message
},
auth
}
// Most messages are meant for a specific thread
if (chatResponse.threadName) {
payload.requestBody.thread = {
name: chatResponse.threadName
}
}
try {
await chat.spaces.messages.create(payload)
console.log('Message successfully sent to the Hangouts Chat API.')
} catch (error) {
console.error(new Error(`Message could not be sent to the Hangouts Chat API due to: ${error}`))
}
}
module.exports = {
sendResponse
}

Using this in the reminder-bot-sender.js Cloud Function is as simple as importing the module and calling await sendResponse(chatResponse) at the end of the function.

Caution: With that, the code is in place to send a response back to Hangouts Chat. However, if you were to deploy the function, it would fail with the following error message:
Error: Message could not be sent to the Hangouts Chat API due to: Error: A Forbidden error was returned while attempting to retrieve an access token for the Compute Engine built-in service account. This may be because the Compute Engine instance does not have the correct permission scopes specified. Request had insufficient authentication scopes.

Long story short, we need a dedicated service account to authenticate the API call. The docs have a chapter on that, but let me explain it in detail, specifically for Node.js.

  1. Create a service account and private key by following the official docs.
  2. Store the key at packages/reminder-bot-sender/service-account.json
  3. Important: Add service-account.json to the .gitignore file. Never, under any circumstances, commit a service account key to your version control system — bad things will happen.
  4. Create packages/reminder-bot-sender/.env.yaml and add the following line: GOOGLE_APPLICATION_CREDENTIALS:./service-account.json
  5. Update thepackages/reminder-bot-sender/package.json file by adding --env-vars-file .env.yaml to the end of the deploy script.

Step C & D

Well, sometimes there’s no work required and things just work. Once the Hangouts Chat API receives the response, it takes care of delivering it to the right thread.

Validation: Congratulations! If you’ve followed along so far and landed here, you can now send a message to Reminder Bot and it will respond, letting you know the reminder is set. Likewise, when Reminder Bot is added to a room or conversation, it will send a welcome message.

Summary

What a journey it’s been, eh? You now have an end-to-end implementation of a Hangouts Chat bot. At this point, your imagination is the limit of what you can build.

What I like about this architecture is its modularity. The majority of pieces are nicely decoupled with Cloud Pub/Sub and it handles an increase in traffic smoothly.

👏 ❤️

Next Steps

To complete this series of blog posts, make sure you read the next post where you’ll learn about Cloud Scheduler and how to notify users when one of their reminders is due.

--

--

Mike

I no longer write on Medium. Follow me on X @mootoday or www.mootoday.com for blog posts.