たまたま機会あってSlack appを作らせていただくことになったのですが、
思ってた以上に大変だったのでそこで得られた知見をいくつか簡単にシェアしておきます。
ちなみに自分はhubotとかbotkitとかで簡単なbot作ったことあるレベルで、
とりあえずtoken発行して環境変数突っ込んでスクリプト動かしたらそれっぽいやつできるんでしょ?、くらいのノリからのスタートだったので正直いろいろつっこみどころありそうではあるのですが、何かあれば是非インプットいただければと思います :pray:
詳細なコンセプトや利用方法はこちらの記事に移譲しますが、
ざっくり三行で説明すると、
という感じになります。
機能的にもかなりシンプルで、
くらいで、まぁ正直こんなん半日くらいでさくっと終わるだろ、とそんなふうに考えていた時期が私にもありました :innocent:
普通に自分のワークスペースでだけ動くbotつくる、というだけであれば、ここに書いてあるとおりにSlack appつくって Bot OAuth Access Token
コピーするだけで大体は事足りると思うのですが、
(↑はhubot用の解説ですがtoken取得するという意味ではあまり変わらないはず)
いろいろなワークスペースで利用されることを前提とする場合、ちゃんとOAuthを利用してtokenを取得できる必要があったり、Slash CommandsやInteractive Components(ボタンとかそういうやつ)利用したい場合は別途設定が必要だったりと、かつて1に比べてかなり設定が煩雑になっており、
そのあたりの日本語のリソースもあまりみかけないので、正直このあたりどうするのがいいのかよくわからんという人も多いのではないでしょうか。
一応Slack appの主な特徴として以下の6つがあるのですが、
細かいことはおいておいて、とりあえず全体感掴むためには、
上記チュートリアルを一通りやると、設定周りからコードの雰囲気までは大体理解できるのではと思います。(英語 + Node.jsになってしまいますが)
Events APIも大体Slash CommandsとInteractive Componentsと同じような雰囲気なのですが、一点、URL設定するタイミングでそのURLがちゃんとレスポンス返さないとエラーになったりするので(refs: https://api.slack.com/events-api#subscriptions) ご注意ください。
(その他詳細な解説は今回は時間の関係上割愛します:bow:)
これ全体通して調査に時間かかったり手戻りする原因になったりしたのですが、そもそもの方針として、セキュリティ観点からなるべくアプリ側にデータ持たないで実装したいというのがあり、APIで完結できるものはなるべくAPIで完結させようと思っていたので、
たとえば、毎月絵文字の集計をするという要件を満たす方法はいくつかあると思うのですが、APIで特定の絵文字がついたメッセージを取得してきてそれを集計できるのであればそれが楽かなと思い、
ぱっと見ちょうどいいAPIがあるのでこれ使えばいけるだろ、と思ってよくよくドキュメントをみてみると、
絵文字や期間でフィルタリングできなさそうで、このAPIだけでやろうと思えばできなくはなさそうですが無駄にリクエスト投げまくらないといけないのと、APIのRate Limitや条件のハンドリングなどやたらと煩雑になりそうだったため、結局リアクションの追加/削除のイベントをsubscribeしてそれをDBにレコードとしてつっこんで集計するようにしてます。
また、設定する絵文字の存在確認をするために絵文字一覧とか取得したくなると思うのですが、
ぱっと見ちょうどいいAPIがあるのでこれ使えばいけるだろ、と思って実装に組み込んでみるとほとんどバリデーションで弾かれてしまい、何だこれと思ってよくよくドキュメント呼んでみると、どうもカスタム絵文字しか返してくれないらしく、そもそもコンセプト的にはカスタム絵文字設定してほしいのでカスタム絵文字限定とかにしても良かったのですが、一旦ここのバリデーションは特になしとしました (設定完了後にメッセージで絵文字返すのでそこでちゃんと絵文字になってなければ設定ミスってても気づくはず...)
などなど、意外とAPIだけで完結させようと思うと難しく、アプリケーション側でなにかしらデータ持つなどしてうまいことやらないといけないパターンが多そうという印象でした。
なんとなくで着手すると意外とそれできないんかいというのでハマったりするのでちゃんとドキュメント読んでから作業することをおすすめします:innocent: (当たり前)
特定の絵文字でリアクションされたかどうか判別するために、 Events APIでreaction_add
、 reaction_remove
イベントをsubscribeして処理をハンドリングするようにしているのですが、
絵文字のエイリアスが設定されている場合かつエイリアスの絵文字文字列でリアクションした場合、エイリアスではなくオリジナルの絵文字文字列で返ってきます。
(ex: :ok_woman:
-> :woman-gesturing-ok:
)
せめてエイリアスあった場合にエイリアスも一緒に返してきてくれると嬉しかったりするのですが、そんな親切仕様ではないので、特定の絵文字に対してなにかしらハンドリングしたい場合は真面目にやるならきちんとこのエイリアスまわりのマッピングを何かしらアプリケーション側でもつ必要がありそうです。
(cf: How can I get the FULL list of slack emoji through API? - Stack Overflow)
とりあえず今回の実装ではエイリアス問題には対応できてません:cry:
これマジで困りました。
具体的に言うと、
channels.history method | Slack
このAPIでリアクションついたメッセージの詳細取りに行ってるのですが、
This method returns a portion of message events from the specified public channel.
↑のように、publicチャンネルのメッセージしか返さないよと言ってるのに何故かとあるprivateチャンネルのメッセージだけ取れてしまうという、しかも全然再現性なくて他のprivateチャンネルでは発生せず、おそらくSlack側の何かしらのバグなのではないかと思ってます。
とはいえうっかりprivateチャンネルのセンシティブなメッセージがオープンになるみたいな事故は起きてもらっては困るので、あれこれ手を動かした結果、
conversations.list method | Slack
このAPIを叩いてpublicチャンネルの一覧を取得して、一覧に含まれていなければprivateチャンネルとみなして処理を通さない、という実装に落ち着きました。
ちなみに似たような名前の
こっちだと件のprivateチャンネルも一緒に返ってきてしまって使えません。
(そもそもよくみると Don't use this method. Use conversations.list instead.
って書いてあったりするのですが、もう少しわかりやすくしておいてくれても良い気がする...)
どの絵文字をトリガーにして、どのチャンネルに転送するかをユーザーが設定できる必要があり、(デフォルトは絵文字: :+1:、 チャンネル: #general)
普通にSlash Commandsで登録するでも良かったのですが、
ドキュメントいろいろ眺めていたところ Dialogs なるものを発見、簡単に言うとSlack上でフォーム的なUIを実現できるものです。
バリデーション等考えるとUX的にはこちらのほうが良さそうな気がしたので(エラー表示とか)
とりあえず実装してみたのですが、
チャンネル選択をselectにした部分は良かったものの、絵文字入力用のテキストフォームで絵文字補完が効かず、これなら普通にSlash Commandsのほうが普通に補完効くし良さそうということで、結局Slash Commandsでの設定に落ち着きました。
(正直なにをいってるか文字ベースだと超わかりにくいのですが、実例はホメルくんをインストールしてもらって /homeru setting
とか叩いてもらえればわかるかと思います:bow:)
Dialogs自体は使い所を見極めればかなり使える機能ではと思います。
メッセージの転送をするために特定のチャンネルに対して何かしらメッセージ送れる機能を実現する必要がありますが、
Legacyな方のIncoming Webhooksは昔からよく使っていて、適当なサーバー側の処理結果をSlackに飛ばすみたいなことをよくやっており、チャンネルや発言者の設定等いろいろ細かく指定できたので、とりあえずIncoming Webhookつかえば大丈夫だろうと実装してみたのですが、
どうもLegacyじゃない方のIncoming Webhookはチャンネル等の指定ができないことが判明し、
You cannot override the default channel (chosen by the user who installed your app), username, or icon when you're using Incoming Webhooks to post messages. Instead, these values will always inherit from the associated Slack app configuration.
(refs: https://api.slack.com/incoming-webhooks)
チャンネル自体の変更もAPIではできなさそうだったので今回の用途としてはIncoming Webhookは使えないことが判明。
あれこれ調べた結果、普通にbotであれば特にチャンネルにinviteしていなくてもメッセージ送信できるようだったのでそちらで回避しました。
@username
をメッセージに含めたいSlackでは基本的に @username
や #channel_name
などはそのままの文字列ではなくIDとして渡ってきます。 (ex: U37UED2RL
)
これらを利用してメッセージにユーザー名やチャンネル名を含めようとした場合、以下のような記法を利用すると、Slackがよしなに変換してくれます。
ex)
- <@U37UED2RL>
=> @uehara
- <#C7F5U1EKE>
=> #general
(refs: Basic message formatting | Slack)
ホメルくんのメッセージ転送機能で、誰が発言したかもわかるようにユーザー名も転送メッセージに一緒に含めたかったのですが、単純に↑の機能を使うと毎回メンション飛んできてこれはうざいという結論になり、なんとかメンション飛ばさないでユーザー名表示できないかあれこれ調べた結果、
このAPI叩けばユーザー名取得できることが判明、よしこれでいけると思ったのですが現実はそう甘くなく、
{
"ok": true,
"user": {
"id": "W012A3CDE",
"team_id": "T012AB3C4",
"name": "spengler",
"deleted": false,
"color": "9f69e7",
"real_name": "Egon Spengler",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"avatar_hash": "ge3b51ca72de",
"status_text": "Print is dead",
"status_emoji": ":books:",
"real_name": "Egon Spengler",
"display_name": "spengler",
"real_name_normalized": "Egon Spengler",
"display_name_normalized": "spengler",
"email": "[email protected]",
"image_24": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
"image_32": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
"image_48": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
"image_72": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
"image_192": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
"image_512": "https://.../avatar/e3b51ca72dee4ef87916ae2b9240df50.jpg",
"team": "T012AB3C4"
},
"is_admin": true,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": false,
"updated": 1502138686,
"is_app_user": false,
"has_2fa": false
}
}
こんなレスポンス返ってくるのですが、 user.profile.display_name
と user.profile.real_name
の2種類があり、 user.profile.display_name
が設定されている人は display_name
、そうでない場合は user.profile.real_name
を表示するというようにしないとSlack上の表示とずれるので注意が必要です。
ちなみに小ネタとして、 attachments
の author_name
フィールドは <@U37UED2RL>
でもメンションにならないのですが、今回は表示上の問題で利用は断念しました。
(refs: Attaching content and links to messages)
ts
がユニークSlackでメッセージデータ取得する際、
{
"type": "message",
"channel": "C2147483705",
"user": "U2147483697",
"text": "Hello world",
"ts": "1355517523.000005"
}
こんなかんじでデータが取れるのですが、 ts
はtimestampの略なんだろうなとは思いつつ、ほぼ同時に同じメッセージ送った場合、かぶったりしないのか、判別可能なのか、仕様どうなってるんだと思って調べたときに地味に言及している部分見つけるの苦労したので一応ソースを貼っておきます。
ts
is the unique (per-channel) timestamp.
(refs: message event | Slack)
結論としては ts
はチャンネルごとにユニーク性が担保されるとのこと。
さて、そんなこんなで思いの外時間かかりつつも一応動くようになったホメルくんですが、そのままでは他のワークスペースにシェアできません。
とりあえず公開して他のワークスペースで利用してもらうというだけであれば、
アプリ設定画面の左カラムに Manage Distribute
というリンクがあるので、そのページ内の
Share Your App with Other Teams
ブロックの Activate Public Distribution
ボタンを押すとシェア用のURLが発行され、これをクリックすると他のワークスペースにアプリをインストールすることが可能です。
ちなみにURLに利用するスコープがパラメータとして付与されており、スコープを変更するとURLも都度変更になるので、パーミッション周りをいじったあとは注意が必要です。
(実際の設定ではなくURLパラメータのスコープが優先される模様、不要なスコープ要求したり逆に足りないといったことが起こりえます)
とりあえず上記だけで他のユーザーに使ってもらえる状態にはなったのですが、
それだけの場合、アプリのページやアプリインストール時のページに以下のような警告文言が出ます。
またApp Directoryのページで検索しても出てきません。
知り合いにだけ使ってもらうレベルであれば問題ないですが、
ちゃんと一般にリリースしようと思った場合はSlackにアプリの審査してもらう必要があります。
審査に出すためにはいくつか必須で設定必要な項目があり、アプリ設定のBasic Informationページで Display Information
と Your Contact Information
の入力、OAuth & Permissionsページでscopeを有効にしている場合はscope毎にそのscopeを利用する理由の入力、その上で、Manage Distributionページの Submit to the Slack App Directory
ブロックにある鬼のような大量のチェックボックスをひたすらクリックしまくると晴れてSubmitできるようになります。
(refs: Submitting apps to the Slack App Directory | Slack)
そんなこんなで審査に出すとその日のうちに結果が返ってきました (早い!)
こんな感じで項目ごとに心温まるメッセージをいただけます。Thanks!
以下、突っ込まれたところを列挙します
s/slack/Slack/g
Slack
文字列を使う場合、頭文字は大文字にしないと怒られますhttps://my.slack.com
に飛ばすようにしていたのですが、ブラウザで利用してない人もいるので https://api.slack.com/docs/deep-linking#app_or_bot こちら参考にして適切な飛ばし先にリダイレクトしてあげてくれとのこと、とりあえず今回はThanksページ作って対応 (ほぼLPそのままですが)channels:read
スコープはbot
スコープに含まれてるから不要
そんなこんなで2度ほど申請対応して晴れて審査通って今回リリースできました:tada:
(ちなみにThanksメッセージのフィードバック対応は当日わりとすぐにレスポンス返ってきたのですが、もろもろ対応したあとに再度申請して実際に承認されるまでは丸一日以上かかりました)
いかがでしたでしょうか、本当はもっと細かい粒度で記事分けてスクショフル活用しながら詳細書きたいところも多々あるのですが、めんど(ry諸事情によりかなり端折ってしまったので正直わかりにくいところも多かったかもしれません。
コードに関してはオープンにしてもOKと寛大なお言葉をいただいてるので、そのうちコード自体公開する予定ではあるのですが、時間制約の都合上かなり雑なコードに仕上がっているので流石にもうちょっと整理してから公開する予定です:bow:
とりあえず興味ある方はTwitterでDMいただければ今の雑なやつでも個別にシェアしますので気軽にお声がけいただければと思います。
Slack app、直近まだまだ開発しやすい環境が整っているとは言いにくい状態という印象ですが(scope変更を含むバージョンアップしたくなったときどうしたらいいんだろうとかとか)、
Slackがかなり多くの企業やユーザーに利用されているプラットフォームではあるのは間違いないのと、まだまだアプリのマーケットプレイスとしては黎明期で何かしら参入できる余地かなり大きいと思うので、個人開発者にとっても今からiOS/Androidアプリ作るよりはワンチャンあるのではと思ってます。
(ただし、真面目に課金とかしようと思うとSlack上で決済できる仕組みが今のところなく、何かしら自分で頑張らないといけないのが若干ハードル高そうですが...)
是非この機会にSlack appチャレンジしてみてはいかがでしょうか。
そして是非皆様ホメルくんつかっていただいてフィードバックいただければと思います!
https://homerukun.atengagement.com/
https://slack.com/apps/ADE8ADF6E--
Have a good Slack :tada:
せっかくなので技術的チャレンジ込みで Go + Serverless な感じでやってみようと思ったのですが、思いの外時間かかりそうだったので手っ取り早く実装できるRailsに落ち着いてしまいました...
大体の雰囲気は掴んだので次回はそちらで挑戦してみたい。
hubotとかが出始めた頃と比較して、当時の機能は今はほとんどDeprecatedになってますね... ↩︎
Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。
また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!
こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?
コメント