1日9時間寝たい

本当は10時間寝たいです

Slash Command に対して 3 秒以上かかる処理をする

はじめに

Slash Command は Slack の機能のひとつです。デフォルトでは /remind/invite などがあります。

このようなスラッシュ / から始まるコマンドは自作することもできます。たとえば、あいさつするコマンド:

  • /greet 太郎 と打つと
  • 「こんにちは、太郎。」と返ってくる

を作れます。

Slash Command は、たとえば HTTP をトリガーに起動する Google Cloud Functions を用意して、Slack からのリクエストが来たら適当にレスポンスを返せばいいです。イメージとしては下の図です。

f:id:poyopoyoyon:20200319191124p:plain

しかし、レスポンスは 3 秒以内にしなければいけません*1

それでは、Cloud Functions で時間のかかる処理を行うにはどうすればいいのでしょう?

結論

解決方法のひとつは Slack Tutorial - Slash Commands  |  Cloud Functions Documentation で言及されているように Google Cloud Pub/Sub を使うことです。Slack からのリクエストを受け取る関数とは別にもう一つ関数を用意して、重い処理をそちらに任せます。

この記事では、上の /greet コマンドを

  1. Cloud Functions のみで実装
  2. Cloud Functions + Cloud Pub/Sub を使って実装

します。

たかが /greet コマンドに (2) の方針を採るのは正直大げさすぎますが、ソースコードの例をシンプルにしたいので /greet コマンドで説明します。

この記事の内容は、以下の [1], [2], [3] から少しづつ拾い集めただけなので、お急ぎの人はリンク先を追ってください。

Cloud Functions

実装と言っても、ソースコードは以下の 4 行で終わりです。

exports.greet = (req, res) => {
    const name = req.body.text;
    res.status(200).send("こんにちは、" + name + "。");
}

マウスをカチカチするパートはだいたい次のような感じです。

ここまでで、Slack で /greet 太郎 と打つとすぐに「こんにちは、太郎。」と Bot が返信するようになりました。

Cloud Functions + Cloud Pub/Sub

図を示します。

f:id:poyopoyoyon:20200319193003p:plain

「わかった」と返信する部分は簡単です。

新しく知るべきことは主に次の 3 つでしょう。

  1. Cloud Pub/Sub にデータを送る方法
  2. Cloud Pub/Sub からデータを受け取る方法
  3. Slack にあいさつを投稿する方法

(a) と (b) は [3]Quickstart: Using Client Libraries  |  Cloud Pub/Sub Documentation を見るとわかります。

(b) のために Cloud Pub/Sub をトリガーとする Cloud Functions を新しく作りましょう。いえ、その前に topic を作る必要があります。それっぽい箇所を Google Cloud Platform のコンソールから探してください。ここでは my-topic と名前を付けました。

f:id:poyopoyoyon:20200319201301p:plain
topic を作る

Function を作ります。[トリガー] は Pub/Sub、[トピック] は my-topic とします。

f:id:poyopoyoyon:20200319201533p:plain
Pub/Sub をトリガーとした Function を作る

(c) は [3] を見ると、コマンドが実行されたときに Slack から /greet 太郎 と一緒に https://hooks.slack.com/commands/1234/5678 のような URL が送られてくるとわかります。この URL に対して POST リクエストをすればよいです*2

実装です。

  • Publisher
const {PubSub} = require("@google-cloud/pubsub");
exports.publish = (req, res) => {
    const pubsub = new PubSub();
    pubsub.topic("my-topic").publish(
        Buffer.from(JSON.stringify(req.body)));
    res.status(200).send("あとであいさつを返します。");
};
  • Subscriber
const fetch = require("node-fetch");
exports.subscribe = pubsubMessage => {
    const data = JSON.parse(Buffer.from(pubsubMessage.data, "base64"));
    fetch(data.response_url, {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            text: "こんにちは、" + data.text + "。"
        })
    });
};

注意点として、@google-cloud/pubsub などの package を使うために package.jsondependencies のところを以下のように編集してください。

f:id:poyopoyoyon:20200320112229p:plain
package.json

動作例です。