Slack Tutorial - Slash Commands

This tutorial demonstrates using Cloud Functions to implement a Slack Slash Command that searches the Google Knowledge Graph API.

Objectives

  • Create a Slash Command in Slack.
  • Write and deploy an HTTP Cloud Function.
  • Search the Google Knowledge Graph API using the Slash Command.

Costs

This tutorial uses billable components of Cloud Platform, including:

  • Google Cloud Functions

Use the Pricing Calculator to generate a cost estimate based on your projected usage.

Before you begin

  1. Sign in to your Google account.

    If you don't already have one, sign up for a new account.

  2. Select or create a Cloud Platform project.

    Go to the Projects page

  3. Enable billing for your project.

    Enable billing

  4. Enable the Cloud Functions and Google Knowledge Graph Search APIs.

    Enable the APIs

  5. Install and initialize the Cloud SDK.
  6. Update and install gcloud components:
    gcloud components update beta &&
    gcloud components install

Visualizing the flow of data

The flow of data in the Slack Slash Command tutorial application involves several steps:

  1. The user executes the /kg <search_query> Slash Command in a Slack channel.
  2. Slack sends the command payload to the Cloud Function's trigger endpoint.
  3. The Cloud Function sends a request with the user's search query to the Knowledge Graph API.
  4. The Knowledge Graph API responds with any matching results.
  5. The Cloud Function formats the response into a Slack message.
  6. The Cloud Function sends the message back to Slack.
  7. The user sees the formatted response in the Slack channel.

It may help to visualize the steps:

Preparing the application

  1. Create a Knowledge Graph API Key.

  2. Create a Slack Slash Command:

    • Create a Slack App. Associate it with a Slack team where you have permissions to install integrations.
    • Go to Slash commands under the features heading in the left navigation. Click the Create new command button.
    • Enter https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/kgSearch as the URL for the command, where YOUR_REGION is the region where your Cloud Function is deployed and YOUR_PROJECT_ID is your Cloud project ID. Both are visible in your terminal when your function finishes deploying.
  3. Create a Cloud Storage bucket to stage your Cloud Functions files, where [YOUR_STAGING_BUCKET_NAME] is a globally-unique bucket name:

    gsutil mb gs://[YOUR_STAGING_BUCKET_NAME]

  4. Create a directory on your local system for the application code:

    • Linux or Mac OS X:

      mkdir ~/gcf_slack
      cd ~/gcf_slack
      
    • Windows:

      mkdir %HOMEPATH%\gcf_slack
      cd %HOMEPATH%\gcf_slack
      
  5. Download the index.js file from the Cloud Functions sample project on GitHub and save it to the gcf_slack directory.

  6. Create a config.json file in the gcf_slack directory with the following contents:

    {
      "SLACK_TOKEN": "[YOUR_SLACK_TOKEN]",
      "KG_API_KEY": "[YOUR_KG_API_KEY]"
    }
    
    • Replace [YOUR_SLACK_TOKEN] with the verification token provided by Slack in the Basic information page of your app configuration.
    • Replace [YOUR_KG_API_KEY] with the Knowledge Graph API Key you just created.
  7. Create a package.json file by running the following command:

    npm init
    
  8. Install the googleapis dependency by running the following command:

    npm install --save googleapis
    

Understanding the code

Importing dependencies

The application must import several dependencies in order to communicate with Google Cloud Platform services:

Node.js

const config = require('./config.json');
const googleapis = require('googleapis');

// Get a reference to the Knowledge Graph Search component
const kgsearch = googleapis.kgsearch('v1');

Receiving the webhook

The following kgSearch function is exported by the module and is executed when you (or Slack) make an HTTP POST request to the function's endpoint:

Node.js

/**
 * Receive a Slash Command request from Slack.
 *
 * Trigger this function by making a POST request with a payload to:
 * https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/kgsearch
 *
 * @example
 * curl -X POST "https://us-central1.your-project-id.cloudfunctions.net/kgSearch" --data '{"token":"[YOUR_SLACK_TOKEN]","text":"giraffe"}'
 *
 * @param {object} req Cloud Function request object.
 * @param {object} req.body The request payload.
 * @param {string} req.body.token Slack's verification token.
 * @param {string} req.body.text The user's search query.
 * @param {object} res Cloud Function response object.
 */
exports.kgSearch = function kgSearch (req, res) {
  return Promise.resolve()
    .then(() => {
      if (req.method !== 'POST') {
        const error = new Error('Only POST requests are accepted');
        error.code = 405;
        throw error;
      }

      // Verify that this request came from Slack
      verifyWebhook(req.body);

      // Make the request to the Knowledge Graph Search API
      return makeSearchRequest(req.body.text);
    })
    .then((response) => {
      // Send the formatted message back to Slack
      res.json(response);
    })
    .catch((err) => {
      console.error(err);
      res.status(err.code || 500).send(err);
      return Promise.reject(err);
    });
};

The following function authenticates the incoming request by checking for the verification token generated by Slack:

Node.js

/**
 * Verify that the webhook request came from Slack.
 *
 * @param {object} body The body of the request.
 * @param {string} body.token The Slack token to be verified.
 */
function verifyWebhook (body) {
  if (!body || body.token !== config.SLACK_TOKEN) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
}

Querying the Knowledge Graph API

The following function sends a request with the user's search query to the Knowledge Graph API:

Node.js

/**
 * Send the user's search query to the Knowledge Graph API.
 *
 * @param {string} query The user's search query.
 */
function makeSearchRequest (query) {
  return new Promise((resolve, reject) => {
    kgsearch.entities.search({
      auth: config.KG_API_KEY,
      query: query,
      limit: 1
    }, (err, response) => {
      console.log(err);
      if (err) {
        reject(err);
        return;
      }

      // Return a formatted message
      resolve(formatSlackMessage(query, response));
    });
  });
}

Formatting the Slack message

Finally, the following function formats the Knowledge Graph result into a richly formatted Slack message that will be displayed to the user:

Node.js

/**
 * Format the Knowledge Graph API response into a richly formatted Slack message.
 *
 * @param {string} query The user's search query.
 * @param {object} response The response from the Knowledge Graph API.
 * @returns {object} The formatted message.
 */
function formatSlackMessage (query, response) {
  let entity;

  // Extract the first entity from the result list, if any
  if (response && response.itemListElement && response.itemListElement.length > 0) {
    entity = response.itemListElement[0].result;
  }

  // Prepare a rich Slack message
  // See https://api.slack.com/docs/message-formatting
  const slackMessage = {
    response_type: 'in_channel',
    text: `Query: ${query}`,
    attachments: []
  };

  if (entity) {
    const attachment = {
      color: '#3367d6'
    };
    if (entity.name) {
      attachment.title = entity.name;
      if (entity.description) {
        attachment.title = `${attachment.title}: ${entity.description}`;
      }
    }
    if (entity.detailedDescription) {
      if (entity.detailedDescription.url) {
        attachment.title_link = entity.detailedDescription.url;
      }
      if (entity.detailedDescription.articleBody) {
        attachment.text = entity.detailedDescription.articleBody;
      }
    }
    if (entity.image && entity.image.contentUrl) {
      attachment.image_url = entity.image.contentUrl;
    }
    slackMessage.attachments.push(attachment);
  } else {
    slackMessage.attachments.push({
      text: 'No results match your query...'
    });
  }

  return slackMessage;
}

Deploying the functions

  1. To deploy the kgSearch function with an HTTP trigger, run the following command in the gcf_slack directory:

    gcloud beta functions deploy kgSearch --stage-bucket [YOUR_STAGING_BUCKET_NAME] --trigger-http
    

    where [YOUR_STAGING_BUCKET_NAME] is the name of your staging Cloud Storage Bucket.

Using the Slash Command

  1. Test the command manually:

    curl -X POST "https://[YOUR_REGION].[YOUR_PROJECT_ID].cloudfunctions.net/kgSearch" --data '{"token":"[YOUR_SLACK_TOKEN]","text":"giraffe"}'

    where

    • [YOUR_REGION] is the region where your function is deployed. This is visible in your terminal when your function finishes deploying.
    • [YOUR_PROJECT_ID] is your Cloud project ID. This is visible in your terminal when your function finishes deploying.
    • [YOUR_SLACK_TOKEN] is the token provided by Slack in your Slash Command configuration.
  2. Watch the logs to be sure the executions have completed:

    gcloud beta functions logs read --limit 100
    
  3. Type the command into your Slack channel:

    /kg giraffe

Cleaning up

To avoid incurring charges to your Google Cloud Platform account for the resources used in this tutorial:

Deleting the project

The easiest way to eliminate billing is to delete the project you created for the tutorial.

To delete the project:

  1. In the Cloud Platform Console, go to the Projects page.

    Go to the Projects page

  2. In the project list, select the project you want to delete and click Delete project. After selecting the checkbox next to the project name, click
      Delete project
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Deleting the Cloud Functions

Deleting the Cloud Functions removes all Container Engine resources.

To delete a Cloud Function:

gcloud beta functions delete [NAME_OF_FUNCTION]

You can also delete Cloud Functions from the Google Cloud Platform Console.

Send feedback about...

Cloud Functions