How to develop a serverless chatbot (for Hangouts Chat) — Send messages to users
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.
- Intro & architecture
- Process incoming chat messages
- Send messages to users (this post)
- Find reminders & notify users
- Monitoring
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:
- Create a new package for the
reminder-bot-sender
service:npx lerna create reminder-bot-sender
. Keep all default values. - 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.
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.
- Create a service account and private key by following the official docs.
- Store the key at
packages/reminder-bot-sender/service-account.json
- 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. - Create
packages/reminder-bot-sender/.env.yaml
and add the following line:GOOGLE_APPLICATION_CREDENTIALS:./service-account.json
- Update the
packages/reminder-bot-sender/package.json
file by adding--env-vars-file .env.yaml
to the end of thedeploy
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.