@kyanny's blog

My thoughts, my life. Views/opinions are my own.

Slack のリマインダーのメッセージ本文を展開して一覧表示する Slack アプリを作った

Slack のリマインダー機能はもっぱら「メッセージにリマインダーを設定」する方法で使っている。書式が難しい /remind コマンドよりずっと使いやすい。自分自身への DM にリマインドしたい内容を走り書きして、そのメッセージにリマインダーを設定したりする。パブリックチャンネルに誰かが書いたメッセージにリマインダーを設定したりもする。

たくさんのメッセージにリマインダーを設定していると /remind list コマンドで一覧表示したときどれがどれだかわからくなって不便。

f:id:a666666:20180130041128p:plain

そこでメッセージ本文を展開して一覧表示する Slack アプリを作った。 /expanded-reminders というコマンドで呼び出せるようにした。やってみたらこれがなかなか便利。 Complete ボタンもつけてみた。

f:id:a666666:20180130041220p:plain

やりたいことがはっきりしていたので、Slack API のドキュメントをあちこち読みながらとにかく動くものを完成させることを最優先に、二日くらいで作りきった。コードはかなりやっつけ仕事だけど、飽きてモチベーションが消える前にちゃんと欲しいものが手元で動いてる状態まで持っていけた。

github.com

アプリ本体のサーバーは Heroku で動かしている。最初は ENV に access token をセットしていたけど、せっかくなのでアプリを配布して(再)インストールしやすくする方法を調べて access token は oauth2 のプロトコルにのっとって取得する形にしてみた。でも、サーバー側の実装が不特定多数の人に配布して利用してもらえるようにはなっていないので、 ENV で十分というか、むしろ ENV のほうがよかったのかもしれない。

ENV でちょっと不安だったのは、 Slack アプリを作成すると生成される Tokens for Your Workspace というやつの権限がどうなっているかよくわからなくて、自分以外の人がこの Slack アプリをインストールしたときに、すでに俺が自分で Tokens for Your Workspace を ENV にセットしているサーバーが共用されることになるので、俺の権限でリマインダーの中身を見られたら嫌だなぁ、という点。 oauth2 のフローで access token を取得してサーバー側に永続化すると明らかに俺の権限の access token が保存されるのでよりまずいんだけど、なので現状のサーバー側の実装では二回目以降の oauth2 認証時には access token を上書き保存しないようにしたりしている(このへんがやっつけ仕事の最たるもの)

Slack の API について学んだこと

  • Slash Commands を使うためには呼び出す API サーバを指定するが、この API サーバのレスポンスを JSON で返すと Interactive Components も出せる
  • Interactive Components の action button を使う際に Slash Commands と別のエンドポイントを指定する場合は、 action を POST する URL のレスポンスに Slash Commands で呼び出されたときのレスポンスを同じもの(ただし action の実行後の状態のデータ)を返すのがよい
    • Interactive Components でリマインダーの Complete ボタンを表示する場合、そのリマインダーだけ Complete になったので「未完了のリマインダー」の一覧を JSON で返してやると、 Slack の画面上では Complete を押したリマインダーだけがその場で消えたような見た目・振る舞いになる
  • 単一のメッセージの内容を取得するには conversations.history method | Slack のみを使えばよいが、 permissions は channels:history, im:history など個別に許可しないといけない
  • Slack アプリ内におけるメッセージの permalink は https://quipper.slack.com/archives/D18T55D5F/p1517205394000105 のような形式だが、 archives/ の後ろがチャンネルIDで、その後ろのpに続く数字がタイムスタンプ。しかし conversations.hisory メソッドの ts 引数に渡すときは 1000000 で割ってやる必要がある
  • permalink 内のタイムスタンプがどんぴしゃりのはずなのになぜか一個古いメッセージにヒットしてしまうことがあって、 +0.000001 秒足して回避したりした。なぜだ
  • スレッド内の返信メッセージの本文を得るのはやっかいで、まず permalink の形式が異なる https://quipper.slack.com/archives/C0TLNNUV6/p1514366729000098?thread_ts=1514280850.000103&cid=C0TLNNUV6 その上で conversations.replies メソッドを使う必要があるし、 permalink 内に二つあるタイムスタンプも正しく使い分けないといけない。詳しくは https://github.com/kyanny/slack-app-expanded-reminders/blob/master/app.rb#L21-L25 参照
  • Slack API は本家ドキュメントがしっかりしているけど英語しかないし、挙動がいまいちよくわからないことがある。ぐぐっても込み入った情報はあんまり多くないので、トライアンドエラーでがんばるしかない