iOSアプリの新規レビューを通知してくれるSlack Button Appを作った~手順と申請手続き

2015年12月にSlackがApp Directoryを公開しました。今までもコミュニティによるIntegrationの一覧などのページがありましたが、より本格的にサードパーティーの開発を促すためなのか、Slack対応アプリを作って公開するまでの手順が整備されています。

以前に、[GAE/GoでAppStoreのレビューをSlackに通知してくれるやつ](http://qiita.com/yosukesuzuki/items/380669d7ddaf7e579821)というのを作ってましたが、これを作りなおして、slack button app 対応してみましたので、その手順を紹介します。

ちなみにアプリそのものはこちらです。

Bell Apps AppStore Review Notification for Slack
https://bell-apps.appspot.com/

<img width="816" alt="Bell_Apps 2.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/36b96bb4-3c8c-4dda-f649-663461db5b25.png">

iOSアプリ開発者でSlackをチームで使っている人は、ぜひご利用ください。無料です。


# アプリの登録
まずはアプリの登録が必要です。

https://slack.com/apps にアクセスして、「build your own」のところをクリックします。

<img width="806" alt="App_Directory___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/4b5a401f-98b0-79a4-4976-34ffb7d1272f.png">

その後のページで、Get Started with Slack Appsのほうを選択します。

<img width="849" alt="Build_Your_Own___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/becdc274-5399-61ac-46ed-15900dd47404.png">

その後のページの中ごろにあるCreate your Slack appをクリックします。
<img width="696" alt="Getting_started_with_Slack_apps___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/869ce5e3-04e7-23dd-40a9-069503899ea0.png">

自分のアプリに関する情報を埋めたら、完了です。

<img width="637" alt="Slack_API__New_Application___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/220d4695-4656-e1bf-cf5b-0deec6fd3973.png">

完了するとClientIDとSecretがもらえますので、それをアプリケーションで使います。

<img width="649" alt="Slack_API__Edit_Application___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/d33fbe55-3bb4-9254-41aa-707b408e628a.png">

# アプリを作る
## Slack Button のコードを取得

Slack Button Appの説明ページに行きます。
https://api.slack.com/docs/slack-button

するとページ中ごろに、コピペ用のコードが用意されています。なので、このコードをコピーして、自分の紹介サイトの中に貼るだけです。

<img width="587" alt="Slack_Button___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/791a52b9-d131-b0de-f584-32317fd0a35e.png">

## OAuth認証
このボタンをクリックすると、OAuth認証が始まります。Slack側での処理がほとんどです。

終わると、登録しておいたリダイレクトページもしくは、redirect_uriのパラメータで指定しておいたページに戻ってきます。

この時に、code というGETパラメータが付いてきますので、それとClientID,Secretを使って最終的に、imcoming webhook のURLやアクセストークンなどを取得します。

今回はGoogleAppEngine/Go(GAE/Go)で作りましたので、処理は以下のようになっています。

```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パラメータの値を送ると例えば以下のように返ってきます。

```bash

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というプロパティの中身が何らかあれば、エラーとすることにしました。

```go

// 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というよりはアプリ側で実装を入れる話です。

<img width="799" alt="Bell_Apps.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/dd15de26-0b1a-9a47-86a4-7a18e622f5d6.png">

ユーザーにこの辺の設定を完了してもらったら、SlackButtonAppのプロセスはおしまいです。

## 通知内容のカスタマイズ
https://api.slack.com/docs/attachments こちらのattachmentsのページがわかりやすいですが、左側に指定の色で線を付けたり、付随の情報を付けたりできます。

<img width="451" alt="Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/79b864ef-bd6a-fd13-679c-2fc155a66087.png">

下がGoでの実装です。ドキュメントの通りの構成のJSONを作って、incoming webhookにPOSTするだけです。


```go

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 こちらのページから申請が可能です。

<img width="700" alt="Banners_and_Alerts_and_Getting_started_with_Slack_apps___Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/ac55cd40-a177-e48f-39be-736a22e3a43f.png">

けっこう入力項目がありますが、中でもハードル高いなと思ったのは、privacy policyへのリンクが必要なことです。

今回は以前にたまたま英文で書いていたやつがあったので、多少修正して、使いました。

~~一通り入力し終わって、submitしてあとは審査を待つという状態ですが、2週間経っても公開にはいたってません。サポートの人とは色々やり取りしてるんですが、個人が作ったようなのは審査厳しめなのかなあ。~~

## 2016/03/04追記
サポートの人といろいろやり取りして、ちょいちょい修正を加えた結果、審査通りました。

# 課題
下のキャプチャの通りなのですが、１つのchannelに対して複数のアプリのレビュー通知を有効にすると、いったいどのアプリの通知の設定なのかまったくわかりません。これも何らかタイトル付けられないか、サポートに問い合わせたんですが、無理そうでした。

<img width="726" alt="Configure_Bell_Apps_App_Review_Notification___SlackTools_Slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/45686/2fa7a19c-6321-07db-247c-df5e49980e5f.png">

# Slackのサポートの反応が早い
審査はずっと待たされているので、残念なのですが、Slackのサポートはすごいレスポンスがいいのは助かります。技術的な質問すると土日でも、メールが返ってくるので、けっこうコストかけて体制作ってるんだなと思いました。ただし、すべて英文でやり取りする必要はありますが。