Firebase を駆使して Slack の slash command をつくってみた


みなさんこんにちは。Aidemyの刀根です。今回は弊社slackでslash commandを一つ追加してみました。

2018/12/6追記
さらに改良した。のでよかったらどうぞ。
本稿の内容をベースにしたものなので一回本稿を読んだほうがいいかも

何したの?

実は弊社には集中力を持続させるためちょいちょいお昼寝する方が多いのです。
しかし、寝すぎてしまう人もちらほら・・・

そこで、お昼寝する前に、「15分寝る」場合は /sleep 15とコマンドを叩いて、時間が経ったら @here 〇〇さんを起こしましょうと通知できるようにしました。(それをみたひとに起こしてもらうという運用で)

構成

サーバーレスは最高。

事前にやっておくこと

チャンネルIDの取得

conversations.listのテスター
テスターを利用して、通知を行うチャンネルのIDを取得しておきます。

incoming webhookの設定

リマインダーを投稿するチャンネルのincoming webhookを設定して起きます。

slash commandの設定

オリジナルのコマンドを動かせるように設定します。今回は /sleep にしました。

実装

今回はfunctionふたつ用意するだけなのでとてもシンプルです。

一つはhttpリクエストで発火する関数、もう一つはcronjobで発行されるPubSubトピックメッセージで発火する関数です。
詳しくはこちらの記事を見てね。FirebaseでTwitterをbot化する

流れとしては、
1. /sleep をslackで実行すると,httpリクエスト(POST)が飛ぶ
2. httpリクエスト(POST)が飛んできたらリマインダをデータベースに登録。
3. 毎分実行されるcronジョブでリマインダを投稿、データベースからリマインダ情報を削除

という流れになります。

実際に作ったソースコードがこちら

index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as axios from 'axios';
import {myMoment} from './myMoment';//moment-timezoneを利用して作成したクラス。後述
import * as shortid from 'shortid';

// slackのincoming webhookのurl
const URL = 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXX';

// dbに繋がるようにする
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();

// httpリクエストで発火する関数
export const setReminder = functions.https.onRequest(async (req,res) => {
  console.log('fire!');
  try {
    if(req.method === 'POST'){
      const min = parseInt(req.body.text || 30);
      const user_id = req.body.user_id;
      const remindTime = myMoment().add(min, 'minutes').format('X');
      await db.collection('reminders').doc(shortid.generate()).set({
        user_id,
        remindTime
      });
      res.send('reminder is set.');
    }else{
      throw new Error('only post method is accepted');
    }

  } catch (error) {
    console.error('error');
    res.status(500).send(error);
  }
})

// cronjobで発火する関数
export const notifyReminder = functions.pubsub.topic('minutes-tick').onPublish(async (event) => {
  console.log('fire!');
  try {
    const now = myMoment().format('X');
    const queryData = await db.collection('reminders').where('remindTime', '<=', now).orderBy('remindTime', 'asc').get();
    const promises = queryData.docs.map(element => {
      const key = element.id;
      const data = element.data();
      return axios.default.post(URL, {
        text: `<!here> <@${data.user_id}>さんを起こしましょう`
      }).then(async () => {
        console.log(`finish to remind ${data.user_id}`);
        await db.collection('reminders').doc(key).delete();
      }).catch(error => {
        throw error;
      });
    });
    await Promise.all(promises);

  } catch (error) {
    console.error('error');
  }
});

myMoment.ts

import * as moment from 'moment-timezone';

moment.tz.setDefault("Asia/Tokyo");

moment.locale("ja", {
  weekdays: [
    "日曜日",
    "月曜日",
    "火曜日",
    "水曜日",
    "木曜日",
    "金曜日",
    "土曜日"
  ],
  weekdaysShort: ["", "", "", "", "", "", ""]
});

export const myMoment = moment;

動作結果


ちゃんと動作してる!やったね!

まとめ

  • slackにコマンドを追加した
  • 意外と簡単に追加できる
  • とりあえずバックエンドは迷ったら #Firebaseしろ