Slack の /remind を敢えて使わずにリマインダーを自作した話

# まえがき

本記事は [Slack Advent Calendar 2018](https://qiita.com/advent-calendar/2018/slack) の 5日目の記事です。

# 何故自作したの？  

Slack の `/remind` って便利ですよね！（仕事でもプライベートでも愛用しています） 
「`/remind` って何？」ってなった人はこちらの記事を読んでみてください
> [slack の /remind 機能まとめ](https://qiita.com/takesato/items/335061d7945ebd5c23f7)

この時点で普段抱えている課題の解決に繋がったりするかもしれません。

ただ、実は微妙に痒いところに手が届かなかったりするんですよね。
そして、今回私は Slack API と AWS Lambda Function を使って敢えて Slack のリマインダーを自作することにしました。

# /remind でできなくて困ったこと

では、私が「痒いところに手が届かない」と感じたポイントを整理します。

### 1. 投稿者が Slack bot になってしまう

`/remind` は Slack bot の標準機能として提供されています。
なので、当たり前ではあるのですが Slack bot の投稿としてリマインドされてしまいます。

例えば下記のような `/remind` を登録した場合

```
/remind @channel 

忘年会の出欠連絡の締め切りは本日の 19:00 までです！
忘れずに 19:00 までにご連絡ください！

at 18:00 on today
```

リマインドは下記のように表示されます。
<img width="2000" alt="スクリーンショット 2018-11-23 18.00.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/89967/90043c75-197a-2481-6cf1-bccd0594fe33.png">
機能としては全然問題ないのですが
やっぱり**<font color="orange">アイコンもユーザー名も自分の好きなように設定したいじゃないですか。</font>**

### 2. チャンネル宛のリマインドはリマインド設定したことがバレてしまう

例えば *#hoge* チャンネル宛に下記のような `/remind` を登録した場合

```
/remind #hoge @here

そろそろお昼にしませんか？

at 12:00 on every weekday
```

*#hoge* チャンネルには下記のようなメッセージが表示されます。
<img width="2000" alt="スクリーンショット 2018-11-23 23.14.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/89967/e894eaac-7b18-39de-7e3a-1c18b8c988f7.png">

やっぱり機能としては問題ありませんが、
やっぱり**<font color="orange">リマインドを設定したことはチームメンバーにはサプライズにしたいじゃないですか。</font>**

### 3. 登録したリマインドを編集することができない

現在のところ `/remind` で登録したリマインドの内容（文言や宛先や日時）を再編集することはできません。
登録した内容を削除して再登録することになります。

先程の *#hoge* チャンネルへの `/remind` 登録のときに
間違えて**<font color="red">「そろそろお春にしませんか？」</font>**と登録してしまった場合は

1. 間違えた文言のリマインドの登録メッセージがチャンネルに表示される
1. 間違えたからリマインドの削除
1. 正しい文言でリマインド再登録
1. 再度リマインド登録メッセージがチャンネルに表示される

となりますね。
折角ならば**<font color="orange">痕跡を残さずにスマートにリマインドを運用したいじゃないですか。</font>**

**え？気にしすぎ？**

**ですよね笑**

なので、「敢えて」自作した話なのです。
それでは実際に作ったものをご紹介します。

# 実際に作ったもの  

[こちら](https://github.com/tasmaniadecoco/SlackReminder)のリボジトリに

* 今回必要な AWS 系のリソースを作成する CloudFormation のテンプレート
* Lambda Function の index.js  

を格納しています。

自作した Slack リマインダーの仕組みは大体こんな感じです。
![画像作成用.001.jpeg](https://qiita-image-store.s3.amazonaws.com/0/89967/c53b4193-b5bb-9142-8385-3697d96e5fb1.jpeg)

### 登場するエッセンス  

* [CloudWatch Events](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html) 
   * `/remind`の at や on で指定する**「いつリマインドするか」**にあたる部分を設定
   * スケジュールの設定方式については[こちら](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html#RateExpressions) 
   * 設定したスケジュールにしたがって、Lambda Function にイベントを通知する
* [Lambda Function](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html)
  * `/remind` の**「どのチャンネルにどんな内容でリマインドするか」**に当たる部分を設定
  * CloudWatch Events からのイベントが通知されると処理を行う
  * 実際は Slack API を呼び出すだけ
* [Slack API](https://api.slack.com/web)
  * Slack の Workspace の管理者がアプリを登録することで Slack API 用のトークンが払い出されるので、そのトークンを指定して Slack API を呼び出す
  * トークンには呼び出せる API のスコープなどの認可情報が含まれている
  * 今回は [chat.postMessage](https://api.slack.com/methods/chat.postMessage) という API を使用している
* Slack Workspace  
  * 今回リマインドを投稿したい対象のワークスペース  

もっとざっくり分けるならば   

* **Slack にシステムからメッセージを投稿する仕組み**  
* **実行タイミングをスケジューリングできる仕組み**  

の組み合わせです。
では、それぞれの仕組みについてもう少しだけ詳しく見ていきましょう。

### ⬛️ Slack にシステムからメッセージを投稿する仕組み

基本的に下記の記事を参考にさせていただき、簡単に作ることができました。  
> [Slack のアカウントを持たざる人（モノ）向けに匿名で投稿できる仕組みを作る](https://qiita.com/OMOIKANESAN/items/1682317f33100094700e)

では、実際の Lambda Function のコードを見てみましょう。

```js:index.js

const axios = require('axios')

exports.handler = async (event, context) => {
    try {
        const url = `https://slack.com/api/chat.postMessage`;
        const params = {
            "channel": "*****", //投稿したいチャンネルのチャンネルID
            "text": "リマインドだよ！", 
            "icon_emoji": ":slack:", 
            "username": "AdventCalendarReminder" 
        }

        const config = {
            headers:{
                'Authorization': "Bearer *******", //Slack API のトークン
                'Content-type': 'application/json' 
            }
        }

        await axios.post(url, params, config);
    } catch (err) {
        console.log(err);
    }
};

```

チャンネルIDを取得する方法は下記の記事を参考にさせていただきました。  

> [Slack — APIに使う「チャンネルID」を取得する方法](https://qiita.com/YumaInaura/items/0c4f4adb33eb21032c08)

こちらの Lambda Function 単体実行で Slack にメッセージが投稿される状態が出来上がります。

### ⬛️ 実行タイミングをスケジューリングできる仕組み

これはもはや CloudWatch Events の機能そのものがカバーしているので
いつ実行するか？のスケジュールの設定と実行したい Lambda Function を指定すれば完了です。
<img width="3000" alt="スクリーンショット 2018-11-24 2.24.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/89967/5ff1cde3-54ae-8dd7-afb1-7690fed08175.png">

# 自作したリマインダーでできるようになったこと

今回の自作リマインダーで解決したかったことは下記の３点でした。

* **投稿者が Slack bot になってしまう**
* **チャンネル宛のリマインドはリマインド設定したことがバレてしまう**
* **登録したリマインドを編集することができない**

そして、それぞれ

* **投稿者が Slack bot になってしまう**  
  * <font color="red">Slack API::chat.postMessage の *username* と *icon_emoji* でカスタマイズ可能</red> 
* **チャンネル宛のリマインドはリマインド設定したことがバレてしまう**
  * <font color="red">基本的に AWS 側に設定するだけだからバレない！</font>
* **登録したリマインドを編集することができない**
  * <font color="red">基本的に Lambda Function の実装を変更すれば後から編集可能</font>

といったように解決されました。

また、Lambda Function で Slack API を呼び出すだけなので、
投稿内容や投稿先のチャンネル等々に条件分岐をつけたり、別の API で取得した内容を投稿するなど、できることはいっぱい増えた気がします。
(というかリマインダーに限定して考えなくてもいいですね笑)

# 自作したリマインダーでは（今のところ）できないこと

冒頭でも書いた通り、私は `/remind` を仕事でもプライベートでも愛用しています。
理由は、ちょっとしたリマインド設定ならばほぼ間違いなく `/remind` で事足りるからです。
（実際態々作らなくても・・・という場面はいっぱいあるので。）

そして、今回自作したリマインダーにはそれはそれで不便な点もあるのです。

* Slack の Workspace に Slack API のアプリを登録する権限を持っている必要がある
* AWS の環境が必要
* **リマインドのリスト機能、リマインドの削除機能がない**
  * `/remind list` でリマインド一覧を表示 => 不要なリマインドの削除機能は偉大  
  * この機能を自作しようと思うとそれなりに面倒ではあると思います

# おわりに

今回の記事を書こうと思ったきっかけとしては
Slack の `/remind` とても便利な割には意外に知られていないので、もっと知ってもらいたいという気持ちが大前提にありました。

その上で、"敢えて"同じようなことを実現してくれるものを自作してみたというお話を書かせていただきました。
Slack はアプリ連携が豊富な上、Slack API を提供してくれているのでハックが捗るエンジニアとしてはワクワクするチャットツールだと個人的に思っています。
この記事を読んでくださった皆様の Slack ライフがより楽しくなることを心から願っております！