slackでシフト提出ボットを作ってみようとした

Qiita初投稿です。

2020年にもシフトを紙で提出しているアルバイトの存在を知りました。
そこで、slackでシフト提出を行い、努力目標としてシフト表を自動生成できるようなbotを作ろうとしました。
![シフトマネージャー.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/78a3ae8b-80f8-2f95-b4ab-27ac6a27de86.gif "実際の作ったボット")
↑実際の作ったボット

結局断念しました。その理由も後に書いていきます。


今回はnode-slack-sdkのAPIではなく、boltを使ってSlackBotを作る方法を書いていこうと思います。

# BoltでのSlackBotの作成方法

最初のSlackBotの作成方法や設定方法などは省略します。

## @slack/boltパッケージのインストール

```console
npm init
npm i @slack/bolt
npm i dotenv
```

## Signing SecretとボットTokenを、環境変数もしくは.envファイルに書き込む

今回は.envファイルに書き込みました。

```text:.env
SLACK_SIGNING_SECRET=<your-signing-secret>
SLACK_BOT_TOKEN=xoxb-<your-bot-token>
```

Signing SecretとボットTokenはそれぞれBasic Information ページとOAuth & Permissions ページから取得できます。（下記画像参照）

![コメント 2020-04-26 114001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/965e5c7a-078b-8014-7e8a-ee0a27bade02.png "Signing Secret")
↑Sining Secret

![コメント 2020-04-26 114654.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/878f075c-37bc-7062-7154-885a5c18590d.png "ボットToken")
↑ボットToken

## コードの中身

ショートカットで１個目のモーダルを表示して送信すると、２個目のモーダルを表示します

```javascript:server.js
const { App } = require('@slack/bolt');
require('dotenv').config();
const app = new App({
  token: process.env.SLACK_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

// Callback IDがsubmitのショートカットに反応する(後述）
app.shortcut('submit', async ({ shortcut, ack, context }) => {
  // グローバルショートカットリクエストの確認
  ack();
  
  try {
    // 組み込みの WebClient を使って views.open API メソッドを呼び出す
    const result = await app.client.views.open({
      // `context` オブジェクトに保持されたトークンを使用
      token: context.botToken,
      trigger_id: shortcut.trigger_id,
      view: {
        type : "modal",
        callback_id : "submitPeriod",
        title : {
          type : "plain_text",
          text : "シフトを提出する期間"
        },
        close : {
          type : "plain_text",
          text : "キャンセル"
        },
        submit : {
          type : "plain_text",
          text : "確定"
        },
        blocks : [
          {
            type : "input",
            block_id : "inputFrom",
            label : {
              type : "plain_text",
              text : "何日から？"
            },
            element : {
              type : "datepicker",
              action_id : "inputFrom",
              
            }
          },
          {
            type : "input",
            block_id : "inputTo",
            label : {
              type : "plain_text",
              text : "何日まで？"
            },
            element : {
              type : "datepicker",
              action_id : "inputTo",
            }
          },
        ]
      }
    });
  }
  catch (error) {
    console.error(error);
  }

  //1個目のモーダルが送信されたら反応
  app.view("submitPeriod", async ({ ack, body, view, context })=>{
    await ack()
    try{
      const period = view.state.values
      const from = period.inputFrom.inputFrom.selected_date
      const to = period.inputTo.inputTo.selected_date
      const result = await app.client.views.open({
        // `context` オブジェクトに保持されたトークンを使用
        token: context.botToken,
        trigger_id: body.trigger_id,
        view: {
          "type": "modal",
          "callback_id" : "submitShift",
          "title": {
            "type": "plain_text",
            "text": "シフトを入力"
          },
          "close": {
            "type": "plain_text",
            "text": "キャンセル"
          },
          "submit" : {
            "type" : "plain_text",
            "text" : "提出"
          },
          "blocks": [
            {
              "type": "actions",
              "elements": [
                {
                  "type": "datepicker",
                  "initial_date": from,
                  "placeholder": {
                    "type": "plain_text",
                    "text": "Select a date",
                    "emoji": true
                  }
                },
                {
                  "type": "datepicker",
                  "initial_date": to,
                  "placeholder": {
                    "type": "plain_text",
                    "text": "Select a date",
                    "emoji": true
                  }
                }
              ]
            },
            {
              "type": "divider"
            },
            {
              "type": "context",
              "elements": [
                {
                  "type": "mrkdwn",
                  "text": "2020-04-01"
                }
              ]
            },
            {
              "type": "actions",
              "elements": [
                {
                  "type": "static_select",
                  "placeholder": {
                    "type": "plain_text",
                    "text": "開始時間（時）",
                    "emoji": true
                  },
                  "options": [
                    {
                      "text": {
                        "type": "plain_text",
                        "text": "0時",
                        "emoji": true
                      },
                      "value": "value-0"
                    },
                    {
                      "text": {
                        "type": "plain_text",
                        "text": "1時",
                        "emoji": true
                      },
                      "value": "value-1"
                    },
                  ]
                },
                {
                  "type": "static_select",
                  "placeholder": {
                    "type": "plain_text",
                    "text": "開始時間（分）",
                    "emoji": true
                  },
                  "options" : [
                      {
                        "text": {
                          "type": "plain_text",
                          "text": "0分",
                          "emoji": true
                        },
                        "value": "value-0"
                      },
                  ]
                },
                {
                  "type": "static_select",
                  "placeholder": {
                    "type": "plain_text",
                    "text": "終了時間（時）",
                    "emoji": true
                  },
                  "options": [
                    {
                      "text": {
                        "type": "plain_text",
                        "text": "0時",
                        "emoji": true
                      },
                      "value": "value-0"
                    },
                  ]
                },
                {
                  "type": "static_select",
                  "placeholder": {
                    "type": "plain_text",
                    "text": "終了時間（分）",
                    "emoji": true
                  },
                  "options" : [
                      {
                        "text": {
                          "type": "plain_text",
                          "text": "0分",
                          "emoji": true
                        },
                        "value": "value-0"
                      },
                  ]
                }
              ]
            }
          ]
        }
      });
      // console.log(result)
    }
    catch(err){
      console.log(err)
    }
  })

  app.view("submitShift", async ({ ack, body, view, context })=>{
    await ack()
    try{
      //ここにシフトを提出したときの処理
    }
    catch(err){
      console.log(err)
    }
  })

});

(async () => {
  // Start your app
  await app.start(process.env.PORT || 3000);
  console.log('⚡️ Bolt app is running!');
})();

```

## 少し設定

Slack apiの画面で、

### Interactivity&Shortcuts

![inter&shortcuts.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/f17983e2-79b4-fa4f-9711-3c2637bcb1bd.png)

### Event Subscriptions

![event.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/6b57e0e4-8dbd-ef53-f87b-7db9faa092db.png)


参考: [Slack Bolt](https://slack.dev/bolt/ja-jp/tutorial/getting-started)