Bolt+jsx-slack+Glitchで伝書鳩botをつくってみた

# はじめに

@75asa さんの誘いで入部した社内部活「OSS部」の活動の一貫として作成する事になりました。
Slackbotは以前Hubotを少し触ったことがある程度で、ほぼ初チャレンジです。

# 使用技術

* 環境
  * macOS 10.15.7
* 言語
  * Node.js
  * JavaScript
* FrameWork
  * Slack Bolt for JS
* PaaS
  * Glitch

# 実装

まず今回のSlackAppを作成するにあたって、下記の記事を参考にさせていただきました。
jsx-slackの開発者様の記事で、初めてでも非常に分かりやすかったです。感謝。

> 参考：[実践 jsx-slack: jsx-slack + Bolt で Slack のモーダルを自在に操ろう](https://tech.speee.jp/entry/2019/10/16/100022)

ディレクトリ構成はこんな感じ。

```
./
├── .env
├── .gitignore
├── app.js
├── node_modules
├── package-lock.json
└── package.json
```

## package.json

```json
{
  "name": "pigeon-message",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "sync": "sync-glitch"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@slack/bolt": "^2.4.1",
    "@speee-js/jsx-slack": "^2.6.0",
    "dotenv": "^8.2.0"
  },
  "devDependencies": {
    "sync-glitch-cli": "^2.0.1"
  }
}
```

## app.js

基本構造としてはこんな感じ

```js

const { App } = require('@slack/bolt');
const { jsxslack } = require('@speee-js/jsx-slack');
require('dotenv').config();

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET
});

// ここに処理を書いていく

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

TimePickerコンポーネント部分で参考記事の通りに進まなかった点が2つほどありました。

* optionsのfunction内にreturn句を追記。
* カスタムコンポーネント定義に必要だった`jsxslack.fragment`はv2以降では削除されていた。
 * https://github.com/speee/jsx-slack/releases/tag/v2.0.0

```js

const options = (count, start, suffix) => {
    return [...Array(count)].map((_, i) => { // returnを追記
        const s = (i + start).toString();
        return jsxslack`
            <Option value="${s}">${s.padStart(2, '0')}${suffix}</Option>
        `
    });
};

const TimePicker = props => jsxslack` // fragmentは不要
    <Section>
        <b>${props.label}</b>
    </Section>
    <Actions id="${props.id}">
        <Select name="hour" value="${props.hour}" placeholder="時">
            <Optgroup label="午前">${options(12, 0, '時')}</Optgroup>
            <Optgroup label="午後">${options(12, 12, '時')}</Optgroup>
        </Select>
        <Select name="minute" value="${props.minute}" placeholder="分">
            ${options(60, 0, '分')}
        </Select>
    </Actions>

    <!-- error message -->
    ${props.error & jsxslack`<Context>:warning: <b>${props.error}</b></Context>`}

    <Input type="hidden" name="hour" value="${props.hour}" />
    <Input type="hidden" name="minute" value="${props.minute}" />
`
```

### カスタマイズ

元記事から追加した点としては、ポスト先のチャンネルを指定できるように、`ConversationsSelect`コンポーネントをモーダル内に追加したのと、ショートカットでモーダルを展開できるようにしました。

![スクリーンショット 2020-12-06 0.28.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336446/5925ab52-d0ec-4eb5-2961-05db3a01f502.png)
![スクリーンショット 2020-12-06 0.28.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336446/40c06c26-9003-69ca-c090-ced3e6166c6a.png)

```js
const modal = props => jsxslack`
    <Modal title="伝言を送る" callbackId="post">
        <Section>
            私にお任せ下さい！
            <Image src="https://source.unsplash.com/ic-13C3QhAI/256x256" alt="鳩" />
        </Section>

        <Textarea id="message" name="message" label="伝言" placeholder="伝言をどうぞ…" required />
        <UsersSelect id="users" name="users" label="送付先" multiple required />
        <ConversationsSelect
            id="channel"
            name="channel"
            label="チャンネル"
            required
            include="public im"
            excludeBotUsers
            responseUrlEnabled
        />
        <DatePicker id="date" name="date" label="日付" required />

        <${TimePicker}
            id="time"
            label="時刻"
            hour="${props.hour}"
            minute="${props.minute}"
            error="${props.timePickerError}"
        />

        <Input type="hidden" name="userId" value="${props.userId}" />
    </Modal>
`
```

ショートカットはSlackAPIのAppsページにある**[Interactivity & Shortcuts]**から追加できました。

![スクリーンショット 2020-12-06 0.27.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336446/65beffcc-0d33-ff53-6566-3a9cb8258eef.png)

```js

app.shortcut('open_modal', ({ ack, body, context }) => {
    ack();

    app.client.views.open({
        token: context.botToken,
        trigger_id: body.trigger_id,
        view: modal({ userId: body.user.id }),
    });
});
```



## Glitchにデプロイ

SlackAppが完成したので[Glitch](https://glitch.com/)にデプロイします。
ちなみに開発中のWebサーバーは[ngrok](https://ngrok.com/)を使ってました。

GitHubにリポジトリを作っておけばGlitchから簡単にImportできるのですが、コード変更のたびにImportしなおすのが面倒なので自動化しました。
![スクリーンショット 2020-12-06 0.07.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336446/509705fa-cfc0-5889-1ff9-b6a6131d14a2.png)

最初は[sync-glitch-cli](https://github.com/glitch-tools/sync-glitch-cli)を使わせてもらったのですが、git pushした後にコマンドラインでsync-glitchを実行する必要がありました。

mainブランチにpushされた時点で自動デプロイが理想でしたので、最終的にはGitHub Actionsにworkflowを追加することにしました。

> 参考：[リポジトリの変更を Glitch に転送する GitHub Actions](https://neos21.hatenablog.com/entry/2020/08/23/080000#Glitch-%E3%81%AE%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88-ID-%E3%81%A8-Auth-Token-%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)

```yml

name: Deploy To Glitch
on:
  push:
    branches:
      - main
    workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy To Glitch
        uses: kanadgupta/glitch-sync@master
        with:
          project-id: ${{ secrets.PROJECT_ID }}
          auth-token: ${{ secrets.AUTH_TOKEN }}

```
はじめ`branches`に`master`と記載しているのに気づかずActionsが実行されませんでしたが、なんとかpushと同時にGlitchへデプロイすることができました。

![スクリーンショット 2020-12-05 23.52.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/336446/1883bfbc-02fd-02b0-640b-6d0e4f14a649.png)


# 参考リンク

[Bolt for JS :zap:](https://slack.dev/bolt-js/ja-jp/tutorial/getting-started)
[Bolt フレームワークを使って Slack Bot を作ろう](https://api.slack.com/lang/ja-jp/hello-world-bolt)
[Reference: Block elements](https://api.slack.com/reference/block-kit/block-elements)
[chat.scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage)
[Block Kit Builder](https://api.slack.com/tools/block-kit-builder)
[jsx-slack→JSON](https://speee-jsx-slack.netlify.com/)
[実践 jsx-slack: jsx-slack + Bolt で Slack のモーダルを自在に操ろう](https://tech.speee.jp/entry/2019/10/16/100022)
[Bolt + jsx-slack + TypeScript を使って Slack App をつくる](https://qiita.com/phigasui/items/63c066e06a1708628ba7)
[ブラウザだけで完結するウェブアプリ作成環境 Glitch](https://laboradian.com/glitch/)