Slackアプリへのメンションを契機にYes/Noボタンを返信して処理を分岐させる

## 概要
メンションを元に外部APIを叩き、取得した情報を元にDBを更新するSlackアプリを作成しました。
[![Image from Gyazo](https://i.gyazo.com/c91da3569bb0ddadd7c40f768c0e4eea.gif)](https://gyazo.com/c91da3569bb0ddadd7c40f768c0e4eea)

## Lambdaの準備
Node.jsでLambdaを作成しています。

```javascript
const axios = require('axios');

const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;

exports.handler = async (event) => {
  console.log(`Event: ${JSON.stringify(event)}`);
  let body = "";
  if (event.isBase64Encoded) {
    // ボタンを押された時のblock_actionsペイロードはbase64でエンコードされているのでそのまま使えない
    const encodePayload = event.body;
    const decodePayload = decodeURIComponent(Buffer.from(encodePayload, "base64").toString("utf-8")).replace('payload=', '');
    body = JSON.parse(decodePayload);
  } else {
    // メンションや疎通確認はエンコードされていないのでそのまま使える
    body = JSON.parse(event.body);
  }

  // Slackアプリからの疎通確認用
  if (body.type === 'url_verification') {
    return {
      statusCode: 200,
      body: body.challenge,
    };
  }

  // Yes No ボタンクリックイベント
  if (body.type === "block_actions") {
    const actionId = body.actions[0].action_id;
    // Yes No ボタンを再度押下されないようにボタンを削除したメッセージに元メッセージを更新する
    const updatedText = decodeURIComponent(body.message.blocks[0].text.text.replace(/\+/g, ' '));
    await updateMessage(body.channel.id, updatedText, body.message.ts);

    // 押されたボタンによって処理の分岐
    let message = "";
    if (actionId === "yes_action") {
      // Yes ボタンが押された場合の処理
      // DB更新処理
      message = "更新が完了しました。";
    } else if (actionId === "no_action") {
      // No ボタンが押された場合の処理
      message = "更新をキャンセルしました。";
    }

    // 更新結果をスレッドに返信
    await sendSlackMessage(body.channel.id, message, body.message.ts);
    return {
      statusCode: 200,
      body: '',
    };
  }

  // メンション時イベント
  if (body.event.type === "app_mention") {
    let message = `<@${body.event.user}>\nデータが見つかりません`;
    let appendBlock = false;
    // Slackから投稿されたidを正規表現を用いて取得
    const matchResult = body.event.text.match(/\d{11}/);

    if (matchResult) {
      const companyId = matchResult[0];
      try {
        // APIから情報を取得処理
        message = `<@${body.event.user}>\n更新対象は ${code}_${companyName} でよろしいですか？\nHubSpot Id : ${companyId}`; 
        appendBlock = true;
      } catch (error) {
        // APIのリクエスト形式に合わなかった場合
        console.log("Failed. companyId : " + companyId);
      }
    }
    // Slackに返信
    await sendSlackMessage(body.event.channel, message, body.event.ts, appendBlock);

    return {
      statusCode: 200,
      body: '',
    };
  }
};


async function sendSlackMessage(channel, text, thread_ts, appendBlock=false) {
  let blocks = [];
  if (appendBlock) {
    // Yes No ボタンの追加
    blocks = [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: text,
        },
      },
      {
        type: "actions",
        elements: [
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "YES",
            },
            "style": "primary",
            value: "yes",
            action_id: "yes_action",
          },
          {
            type: "button",
            text: {
              type: "plain_text",
              text: "NO",
            },
            value: "no",
            action_id: "no_action",
          },
        ],
      },
    ];
  }

  const postData = {
    token: SLACK_BOT_TOKEN,
    channel,
    text,
    thread_ts,
    blocks,
  };

  const response = await axios.post("https://slack.com/api/chat.postMessage", postData, {
    headers: {
      "Content-Type": "application/json; charset=utf-8",
      "Authorization": `Bearer ${SLACK_BOT_TOKEN}`,
    },
  });
  
  return response;
}

// Yes Noボタンを削除するためにメッセージを更新する
async function updateMessage(channel, text, ts) {
  const postData = {
    token: SLACK_BOT_TOKEN,
    channel,
    text,
    ts,
    blocks: JSON.stringify([
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: text,
        },
      },
    ]),
  };

  const response = await axios.post("https://slack.com/api/chat.update", postData, {
    headers: {
      "Content-Type": "application/json; charset=utf-8",
      "Authorization": `Bearer ${SLACK_BOT_TOKEN}`,
    },
  });

  return response;
}

```

このLambdaをSlackアプリから発火させるために関数URLを紐付ける。

[![Image from Gyazo](https://i.gyazo.com/c4eb94f529f60dab49dd8ec339114b55.png)](https://gyazo.com/c4eb94f529f60dab49dd8ec339114b55)

## Slackアプリの準備

### メンションイベントを拾えるようにする

#### Scopes(権限)の設定
[![Image from Gyazo](https://i.gyazo.com/54739e7a72a44e9f58b1d0d2b6041460.png)](https://gyazo.com/54739e7a72a44e9f58b1d0d2b6041460)

#### 権限設定完了後Slackワークスペースにインストール
[![Image from Gyazo](https://i.gyazo.com/8a0868160291d6d43150039b5178b85f.png)](https://gyazo.com/8a0868160291d6d43150039b5178b85f)

#### Event Subscriptionsに登録
[![Image from Gyazo](https://i.gyazo.com/7d3ab58fd325bd4837d91cdcd129f4d8.png)](https://gyazo.com/7d3ab58fd325bd4837d91cdcd129f4d8)

### ボタンイベントを拾えるようにする

### Interactivityを設定
[![Image from Gyazo](https://i.gyazo.com/8057e4a82244fa7102b587f20ab96d73.png)](https://gyazo.com/8057e4a82244fa7102b587f20ab96d73)