Runkit で作るお手軽サーバレス Slack App

いまさらですが Runkit で Slack の投票アプリを作ってみました。なんとサーバレス＆DBレスです。
![Selection_092.png](https://qiita-image-store.s3.amazonaws.com/0/18973/b64ad431-3d8b-fae8-c5c3-3ca16dba8b8d.png)
この記事ではこの投票アプリの作り方を紹介します。
（Runkit を知らない人は [Runkit で作るお手軽サーバレス Web API](https://qiita.com/IzumiSy/items/35da6a48b2b00d0ab95a) からどうぞ）

# 実装コード
Runkit で雑に Notebook を作って以下のコードをコピペしましょう

```js
const express = require("@runkit/runkit/express-endpoint/1.0.0");
const bodyParser = require("body-parser");
const { createMessageAdapter } = require("@slack/interactive-messages")

const SLACK_TOKEN = 'xxxxx';

const app = express(exports);
const slackInteractions = createMessageAdapter(SLACK_TOKEN);

//
// Interactive Message の内容
// 
const interactiveButtons = (text, userName) => ({
  text: `A new polling incoming :raised_hand: by @${userName}`,
  response_type: 'in_channel',
  attachments: [
    {
      text,
      color: '#3AA3E3',
      callback_id: 'yesno_polling',
      actions: [{
        name: 'yesno_polling',
        text: 'Yes! :ok_woman:',
        value: 'yes',
        type: 'button',
        style: 'primary',
      }, {
        name: 'yesno_polling',
        text: 'No :woman-gesturing-no:',
        value: 'no',
        type: 'button',
        style: 'danger',
      }]
    }, {
      color: '#32cd32',
      title: ':ok_woman:',
      callback_id: 'yes_users',
      text: '',
    }, {
      color: '#ff0000',
      title: ':woman-gesturing-no:',
      callback_id: 'no_users',
      text: '',
    }
  ]
});

//
// *上の Interactive Message に対応するハンドラ*
// 
// 投票が被ってしまわないよう自分の既存の選択を一度リセットしたうえで再び該当する選択肢にアサインする
//
slackInteractions.action('yesno_polling', (payload, respond) => {
  const originalMessage = payload.original_message;
  const resetOwnVoting = (text, userName) => (text || '').replace(`@${userName}`, '');
  
  const yesUsers = 
    originalMessage.attachments[1].text =
    resetOwnVoting(originalMessage.attachments[1].text, payload.user.name);
    
  const noUsers = 
    originalMessage.attachments[2].text =
    resetOwnVoting(originalMessage.attachments[2].text, payload.user.name);
  
  switch (payload.actions[0].value) {
    case 'yes':
      originalMessage.attachments[1].text = (yesUsers + ` @${payload.user.name}`);
      break;
    case 'no':
      originalMessage.attachments[2].text = (noUsers + ` @${payload.user.name}`);
      break;
  }

  return originalMessage;
});

//
// Slash Command のリクエストハンドラ
//
const SLACK_ACTION_PATH = '/slack/actions'
const SLACK_COMMANDS_PATH = '/slack/commands'

app.use(bodyParser.urlencoded({ extended: false }));
app.use(SLACK_ACTION_PATH, slackInteractions.expressMiddleware());
app.post(SLACK_COMMANDS_PATH, (req, res, next) => {
  const pollingText = req.body.text;
  const userName = req.body.user_name;
  
  if (pollingText) {
    res.send(interactiveButtons(pollingText, userName));
  } else {
    res.send('_No polling text. That\'s required_');
  }
});
```
DBレスと書きましたが、実際には投票結果の文字列をデータソース代わりに無理やり使っているだけのマヤカシです:tired_face: 

Interactive message buttons の詳しい使い方はここで説明すると大変なのでしません。

ただ、とりあえず実装には [slackapi/node-slack-interactive-messages](https://github.com/slackapi/node-slack-interactive-messages) を使っておくのが良いと思います。今回も使いましたが、めちゃ簡単に作れました。

あとは **Endpoint** から、この Notebook をAPIとして利用するためのURLを取得して、どこかにメモっておきましょう。
![Selection_093.png](https://qiita-image-store.s3.amazonaws.com/0/18973/53d687bd-3ecd-a770-402f-59346ecf9b72.png)

# アプリの作成
[Slack App の登録ページ](https://api.slack.com/apps)から、新しく自分の Slack App を作成しましょう。
まずは App Name に好きな名前を指定してください。
![Selection_094.png](https://qiita-image-store.s3.amazonaws.com/0/18973/f4ce91ac-7711-7e2e-48e8-6eb2bfe9e0d4.png)

## Interactive Components
**Interactivity** を有効化して、その下にある **Request URL** に Notebook から取得した API の URL に `/slack/actions` のパスを付加したものを指定しましょう。
![Selection_095.png](https://qiita-image-store.s3.amazonaws.com/0/18973/e7282c79-936b-94fb-8b20-b26a73f21551.png)

## Slash Commands
僕らのアプリはスラッシュコマンドで起動できるようにしましょう。
まずは **Create New Command** から新しいスラッシュコマンドを作成します。
![Selection_096.png](https://qiita-image-store.s3.amazonaws.com/0/18973/3f6eaca1-0cb4-db38-ae38-56dd32945a56.png)

**Command** にはスラッシュコマンドとして指定したい文字列を指定します。
**Request URL** には Interactive Components でやったように Notebook から取得した URL の末尾に `/slack/commands` を付加したものを指定しましょう。
![Selection_097.png](https://qiita-image-store.s3.amazonaws.com/0/18973/cf74d648-0c60-d942-f8d4-e5b81da1c602.png)

## ベリフィケーション・トークンの設定
**Basic Information** のページから、ベリフィケーション・トークンというのものをコピーして、上で出てきた実装コードの中にある `SLACK_TOKEN` に代入するよう書き換えて下さい。
![Selection_098.png](https://qiita-image-store.s3.amazonaws.com/0/18973/175e293c-4b0a-0480-0632-693c593d2c02.png)
ちなみにこれは秘密のものなので、もし誰かにバレてしまったら **Regenerate** から再生成しましょう。僕はここでおっぴろげに公開してしまったので、たったいま再生成しました:ok_hand: 

## アプリ情報の設定
ベリフィケーション・トークンを取得する画面をもうちょっと下にいくと、アプリ情報を指定できるところがありますね。
ここからアイコンを設定すると、もっとカッコよくできます。僕はとりあえず手元にあったプーマのかっこいいスニーカーをアイコンに指定してみました。カッコいいですね。
![Selection_100.png](https://qiita-image-store.s3.amazonaws.com/0/18973/c378d541-b2e3-bef9-988f-58e23ccf313b.png)

## ワークスペースへのインストール
ここまででほぼ準備は終わりです！！
さっそくワークスペースにインストールしましょう
![Selection_099.png](https://qiita-image-store.s3.amazonaws.com/0/18973/9dd2e9d6-43bd-ad16-d58c-fa0aee7a12a5.png)

# 使ってみる
`mypoll` コマンドで投票アプリくんに投票をつくってもらうことができます。
![Peek 2018-12-11 00-10.gif](https://qiita-image-store.s3.amazonaws.com/0/18973/de7ad2dc-515e-79da-7336-509fa338e30c.gif)
これでいつでも二択の決選投票を楽しめますね。

# 問題点
使っていると、わりと頻繁に Runkit 側から `Timeout was reached` というメッセージを受け取ります。実用的とは言えなさそうですね。