Help us understand the problem. What is going on with this article?

Slash Commands と Slack Interactive Message 入門

More than 1 year has passed since last update.

つくるもの

  • Slash interactive message を使ったインタラクティブな Slack

    • 今回は slash command ですが、もちろん、 outgoing と組み合わせたり bot にしても大丈夫です
  • GolangでSlack Interactive Messageを使ったBotを書く ではコードはかけると思うのですが、 Slack 上での設定が多すぎて迷うと思ったので書きました。

  • 実装側は Deeeet さんので十分です

やること

  • Slack 上での登録/設定
  • Global から Request できる Web サーバ (API)

補足

  • 本番環境は適宜設定するとして、開発環境は ngrok を使うとよいです(が自社のセキュリティルールに合わせてください)
  • mac の場合は brew cask install ngrok で入ります

Slack 上での登録/設定

事前に ngrok http 3000 などを実行し、エンドポイントを取得しておいてください

  1. https://api.slack.com/apps を開く
  2. 「Create New App」で App を作成 image.png
    1. Slack Team を選ぶ
    2. あとで配ったりもできるので、手元のテスト環境用の team で大丈夫です
  3. 「Basic information」->「Add features and functionality」から「Slash Commands」を選択し、下記を設定
    1. Command: Slack 上で 「/XXX」 と入力して app を呼び出すコマンドを決めます
    2. Request URL: app の呼び出しエンドポイントを登録します
    3. その他は適当に
  4. 「Interactive Messages」を設定
    1. Request URL: Button / Menu を選択したときに呼び出されるエンドポイントを登録します

API サーバを書く

  • 今回は適当に node で書いていますが、好きな言語で書いてください。

Slash Command を受けて Button / Menu を返す

  • slash コマンドからの API 呼び出しは POST で来ます
{
  "token":"XXXXXXXXXXXXXXXXX",
  "team_id":"XXXXXXXXXXXX",
  "team_domain":"muddydixon",
  "channel_id":"XXXXXXXXXXX",
  "channel_name":"general",
  "user_id":"XXXXXXXXXXXX",
  "user_name":"muddydixon",
  "command":"/deploy",
  "text":"",
  "response_url":"https://hooks.slack.com/commands/XXXXXXXXX/XXXXXXX/XXXXXXXXXXX
}
  • command を確認して適切な処理に振り分ければ1アプリで複数のコマンドを受けることができます
  • Interactive Message は現時点で Button と Menu を使うことができます。
    • Interactive Messages にサンプルの JSON も載っていますので参考にしてください
  • Button と Menu の場合で返却する JSON が異なるので、適当に messageBuilder を作って簡略化しておきました
  • Slack の該当 channel で設定した slack command をうち、うまくいくと下記のように応答が返ってきます

image.png

Interactive Message のユーザ応答を受ける

  • Interactive Message からの API 呼び出しも POST できます
  • body.payload の中身は文字列化された JSON で parse が必要です (下記には parse した JSON を記載しておきます)
{
  "actions":[{"name": "version", "type": "button", "value": "0.0.2"}],
  "callback_id": "version",
  "team": {"id": "XXXXXXX", "domain": "muddydixon"},
  "channel": {"id": "XXXXXXXXX", "name": "general"},
  "user": {"id": "XXXXXXXXX", "name": "muddydixon"},
  "action_ts": "1499824071.755716",
  "message_ts": "1499824068.033282",
  "attachment_id": "1",
  "token": "XXXXXXXXXXXXXXXXX",
  "is_app_unfurl": false,
  "original_message": {
    "text": "デプロイするバージョン",
    "bot_id": "XXXXXXXXX",
    "attachments": [
      {
        "callback_id": "version",
        "fallback": "choose version",
        "id": 1,
        "actions": [
          {
            "id": "1", "name": "version", "text": "0.0.1",
            "type": "button", "value": "0.0.1", "style": ""
          }, {
            // 
          }
        ]
      }
    ],
    "type": "message",
    "subtype": "bot_message",
    "ts": "1499824068.033282"
  },
  "response_url": "https: //hooks.slack.com/actions/XXXXXXXXXXXX/XXXXXXXXXXXXXXX/XXXXXXXXXXXXXXX"
}
  • レスポンスのルールとしてはきちんと {text: "XXX"}を返して、履歴を残すようにしましょう

サンプルコード

const http = require("http");
const express = require("express");
const BodyParser = require("body-parser");

const app = express();
app.use(BodyParser.json());
app.use(BodyParser.urlencoded({extended: true}));

// body を確認するため
app.use((req, res, next)=>{
  console.log(`${req.path}, ${req.method}, ${JSON.stringify(req.query)}, ${JSON.stringify(req.body)}`);
  next();
});

// 返却する json を組み上げるのが面倒なので builder
const messageBuilder = (text = "", subtext = "", type = "button", name = "choice", actions = [], options = {})=>{
  if(["button", "select"].indexOf(type) === -1) return null;
  return {
    text,
    response_type: options.responseType || "in_channel",
    attachments: [
      {
        text: subtext,
        fallback: options.fallback || `choose ${name}`,
        callback_id: options.callback_id || `${name}`,
        color: options.color || "info",
        attachment_type: "default",
        actions: type === "button" ? actions.map((action)=>{
          return {
            name,
            text: action,
            type,
            value: action.toLowerCase()
          };
        }) : [{
          name,
          type,
          text: `pick a ${name}`,
          options: actions.map((action)=>{
            return {
              text: action,
              value: action
            };
          })
        }]
      }
    ]
  };
};

// commands
const commands = {
  "/deploy": ()=>{
    // ここでなんかの処理を書く
    // ansible 叩くとか

    return messageBuilder(
      "デプロイするバージョン", "", "button", "version",
      ["0.0.1", "0.0.2", "0.0.3"]);
  }
};

Object.keys(commands).forEach((command)=>{
  app.post(`${command}`, (req, res, next)=>{
    if(!req.body) return null;
    if(req.body.command){
      return res.json(commands[req.body.command]());
    }else if(req.body.payload){
      const payload = JSON.parse(req.body.payload);
      const action = payload.actions[0];
      const type = action.type;
      return res.json({
        text: `you choose ${type === "button" ? action.value : action.selected_options[0].value}`
      });
    }
    return res.json({text: "invalid message"});
  });
  app.post(`${command}/options`, (req, res, next)=>{
    res.json({});
  });
});


const server = http.createServer(app);
const port = process.env.PORT || 3000;
server.listen(port);
server.on("listening", ()=>{
  console.log(`listening on ${port}`);
});
server.on("error", (err)=>{
  console.error(err);
});
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした