2015年12月にSlackがApp Directoryを公開しました。今までもコミュニティによるIntegrationの一覧などのページがありましたが、より本格的にサードパーティーの開発を促すためなのか、Slack対応アプリを作って公開するまでの手順が整備されています。
以前に、GAE/GoでAppStoreのレビューをSlackに通知してくれるやつというのを作ってましたが、これを作りなおして、slack button app 対応してみましたので、その手順を紹介します。
ちなみにアプリそのものはこちらです。
Bell Apps AppStore Review Notification for Slack
https://bell-apps.appspot.com/
iOSアプリ開発者でSlackをチームで使っている人は、ぜひご利用ください。無料です。
アプリの登録
まずはアプリの登録が必要です。
https://slack.com/apps にアクセスして、「build your own」のところをクリックします。
その後のページで、Get Started with Slack Appsのほうを選択します。
その後のページの中ごろにあるCreate your Slack appをクリックします。
自分のアプリに関する情報を埋めたら、完了です。
完了するとClientIDとSecretがもらえますので、それをアプリケーションで使います。
アプリを作る
Slack Button のコードを取得
Slack Button Appの説明ページに行きます。
https://api.slack.com/docs/slack-button
するとページ中ごろに、コピペ用のコードが用意されています。なので、このコードをコピーして、自分の紹介サイトの中に貼るだけです。
OAuth認証
このボタンをクリックすると、OAuth認証が始まります。Slack側での処理がほとんどです。
終わると、登録しておいたリダイレクトページもしくは、redirect_uriのパラメータで指定しておいたページに戻ってきます。
この時に、code というGETパラメータが付いてきますので、それとClientID,Secretを使って最終的に、imcoming webhook のURLやアクセストークンなどを取得します。
今回はGoogleAppEngine/Go(GAE/Go)で作りましたので、処理は以下のようになっています。
const clientId = "xxx"
const clientSecret = "xxxx"
func basicAuth() string {
// 文字列をbyte配列にしてbase64にする
data := clientId +":"+clientSecret
sEnc := base64.StdEncoding.EncodeToString([]byte(data))
return sEnc
}
client := urlfetch.Client(ctx)
code := r.URL.Query().Get("code")
req, err := http.NewRequest("GET", "https://slack.com/api/oauth.access?code="+code, nil)
if err != nil {
ren.JSON(w, http.StatusBadRequest, map[string]interface{}{"message": "cannnot make http request"})
return
}
req.Header.Add("Authorization", "Basic "+basicAuth())
resp, err := client.Do(req)
if err != nil {
ren.JSON(w, http.StatusInternalServerError, map[string]interface{}{"message": "failed to get response from Oauth2 request"})
return
}
エラーでも200のステータスコード
ここでプチはまりしたのが、Slackでアクセストークンを要求するAPIがエラーでも400系のエラーを返さず、200を返すようになっていたことです。
間違ったcodeパラメータの値を送ると例えば以下のように返ってきます。
http -f GET https://slack.com/api/oauth.access?code=aaa.bbb Authorization:"Basic xxxxxxxx"
return:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 35
Content-Security-Policy: referrer no-referrer;
Content-Type: application/json; charset=utf-8
Date: Tue, 29 Dec 2015 13:52:18 GMT
Server: Apache
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
{
"error": "invalid_code",
"ok": false
}
Slackに問い合わせると、サポートの人も400系であるべきと認めてましたが、すぐには変えられないとの返事。仕方ないので、errorというプロパティの中身が何らかあれば、エラーとすることにしました。
// IncomingWebhook is a child element of oauth2 response
type IncomingWebhook struct {
URL string `json:"url"`
Channel string `json:"channel"`
ConfigurationURL string `json:"configuration_url"`
}
// AccessToken is a struct for oauth2 response
type AccessToken struct {
AccessToken string `json:"access_token"`
Scope string `json:"scope"`
TeamName string `json:"team_name"`
TeamID string `json:"team_id"`
IncomingWebhook IncomingWebhook `json:"incoming_webhook"`
OK bool `json:"ok"`
Error string `json:"error"`
}
dec := json.NewDecoder(resp.Body)
var jsonData AccessToken
dec.Decode(&jsonData)
if len(jsonData.Error) > 0 {
ren.JSON(w, http.StatusBadRequest, map[string]interface{}{"message": "Slack API does return error:" + jsonData.Error})
return
}
アプリ独自の設定の登録
今回のアプリは、特定のiOSアプリの特定の地域のストアのレビューを通知するものです。なので、アクセストークンやWebhookのアドレスを取得後に、該当するストアのURLを入力してもらうような仕様にしています。ここはもう、Slackというよりはアプリ側で実装を入れる話です。
ユーザーにこの辺の設定を完了してもらったら、SlackButtonAppのプロセスはおしまいです。
通知内容のカスタマイズ
https://api.slack.com/docs/attachments こちらのattachmentsのページがわかりやすいですが、左側に指定の色で線を付けたり、付随の情報を付けたりできます。
下がGoでの実装です。ドキュメントの通りの構成のJSONを作って、incoming webhookにPOSTするだけです。
var fields []map[string]interface{}
starEmoji := ""
for i := 0; i < ar.Star; i++ {
starEmoji = starEmoji + ":star:"
}
fields = append(fields, map[string]interface{}{
"title": "Star:",
"value": starEmoji,
"short": false,
})
fields = append(fields, map[string]interface{}{
"title": "Meta:",
"value": ar.Version,
"short": false,
})
var attachments []map[string]interface{}
attachments = append(attachments, map[string]interface{}{
"fallback": text,
"pretext": rn.Title,
"color": "#8EFCD3",
"title": ar.Title,
"text": ar.Content + "\n",
"fields": fields,
})
payload := map[string]interface{}{"attachments": attachments, "username": "Bell Apps App Review Notification", "icon_url": iconURL, "mrkdwn": false}
payloadJSON, err := json.Marshal(payload)
if err != nil {
log.Infof(ctx, "%v", err)
return
}
b := bytes.NewBuffer(payloadJSON)
req, _ := http.NewRequest("POST", rn.WebhookURL, b)
req.Header.Set("Content-Type", "application/json")
resp, _ := client.Do(req)
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
rn.SetUpCompleted = false
_, err = rn.Update(ctx)
}
ユーザーが連携を無効にしたら
上のコードの最後の部分ですが、ユーザーがincoming webhookの設定を無効にした場合、404が返ってくることになっています。
404が返ってきたら通知ができないので、次から通知しようとしないようにフラグを立てて置きます。
App Directoryへ申請する
https://api.slack.com/slack-apps#directory こちらのページから申請が可能です。
けっこう入力項目がありますが、中でもハードル高いなと思ったのは、privacy policyへのリンクが必要なことです。
今回は以前にたまたま英文で書いていたやつがあったので、多少修正して、使いました。
一通り入力し終わって、submitしてあとは審査を待つという状態ですが、2週間経っても公開にはいたってません。サポートの人とは色々やり取りしてるんですが、個人が作ったようなのは審査厳しめなのかなあ。
2016/03/04追記
サポートの人といろいろやり取りして、ちょいちょい修正を加えた結果、審査通りました。
課題
下のキャプチャの通りなのですが、1つのchannelに対して複数のアプリのレビュー通知を有効にすると、いったいどのアプリの通知の設定なのかまったくわかりません。これも何らかタイトル付けられないか、サポートに問い合わせたんですが、無理そうでした。
Slackのサポートの反応が早い
審査はずっと待たされているので、残念なのですが、Slackのサポートはすごいレスポンスがいいのは助かります。技術的な質問すると土日でも、メールが返ってくるので、けっこうコストかけて体制作ってるんだなと思いました。ただし、すべて英文でやり取りする必要はありますが。