LINE Developer Trialを GAE/Go で始める  #linedevday

![screenshot_.png](https://qiita-image-store.s3.amazonaws.com/0/15818/bee18ff7-f372-62f9-144c-192dda085697.png)


LINE BotのAPIが新しくなりました！
MessageAPIがreleaseされて 新しいメッセージタイプやグループへの招待ができるようになりました。

お試しのTrialBotはこれからはDeveloper Trialとなります。
友達制限が５０人以外は有料のプランと同等の機能が試せるとのことなので 名前の通り開発用ですね。

App Engineを使う理由として豊富な無料枠と標準でHTTPS通信が可能なためです。

そして Bot Trial の時はIPを登録する必要がありましたが 現在はオプショナルとなっているのでIPを登録する必要がなくなってるのでIPが不定のAppEngineでも扱いやすくなりました！！

# 事前準備

Developer Trialを登録するにはLINEアカウントが必要です。
Google App Engineを利用するには事前にGoogleアカウントが必要です。

以下のURLからGAE/Go SDKのダウンロードが必要です。(Homebrewでも入れられます)

https://cloud.google.com/appengine/docs/go/download

[Cloud Console](https://console.cloud.google.com/)で新しいプロジェクトを作成してprojectIDをメモっておきましょう。

# 登録

https://business.line.me/ja/

LINE BUSINESS CENTERから登録できます。
右上のボタンからログインして、ログインできたら右上から [会社/事業者未選択] を選択し
[会社/事業者を追加する]ボタンをクリックして必要事項を記入していきます。

作成できたら MessageAPIをクリックして Developer Trialを選択します。
アカウント名と業種を入力して LINE@ MANAGERに行きます。
Bot設定から[APIを利用する]ボタンを押して有効にします。
そしたら各種設定ができるようになります。

今回はBotの設定を以下のようにしました。

<img width="739" alt="スクリーンショット 2016-09-29 14.17.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/15818/1e156e77-15f1-46f6-b61b-4c0783260399.png">

Webhook送信とBotグループトーク参加を利用するにしました。

またLINEビジネスセンターに戻りアカウントリストから登録したBotアカウントを見つけて、Messaging APIの横に[LINE Developers]のボタンをクリックしてLINE Developersに行きます。
そこに Client Secret と Client Token が手に入るのでメモっておきましょう。

下のほうにいくと EDITとあるので編集画面に行きWebhookに`https://<projectID>.appspot.com/callback`を設定します。

公式サイトに動画があるのでそちらも確認してください。

https://developers.line.me/messaging-api/getting-started

# simple Bot

今回はSDKを使いつつ AppEngineらしいやり方で実装した 単純にEventを受けて "OK"と返すだけのBotを作りました。

## SDKのインストール

イベントのWebhookはJSONで受け取ることができますが 公式に何種類かの言語に対応したSDKが配布されていて その中にGoがあるので それを使います。

インストールは以下のコマンドです。 ついでに今回使うパッケージもインストールしておきます。

```bash
$ go get -u github.com/line/line-bot-sdk-go/linebot
$ go get -u github.com/joho/godotenv
$ go get -u google.golang.org/appengine
```

## app.yaml

適当なディレクトリを作成します。

```
$ mkdir linebot; cd linebot
```

そのディレクトリの中に`app.yaml`を作成します。

```yaml:app.yaml
application: <projectID>
version: 1
runtime: go
api_version: go1

handlers:
- url: /task.*
  script: _go_app
  login: admin
  secure: always
- url: /.*
  script: _go_app
  secure: always
```

`application`のところには 最初に用意したGCPのprojectIDを入れておきましょう 例えば `linebot-example`だったら以下のようになります。

```yaml
application: linebot-example
```

あとはAppEngineのほぼ最小構成に`/task`以下は管理者のみのアクセスに指定限定した設定です。

`/task`には後ほど TaskQueue を使ってアクセスするために設定しています。

## 実装

まずは 設定ファイルの作成をします。

`line.env`というファイルを作成します。

これはChannelSecretなどの秘密情報を書いておくためのファイルです。

```bash:line.env
LINE_BOT_CHANNEL_SECRET=<ChannelSecret>
LINE_BOT_CHANNEL_TOKEN=<AccessToken>
```

このファイルを`godotenv`を使って環境変数に読み込みます。
僕は秘密情報はコードには直接含めずに 暗号化して デプロイ環境で復号化してデプロイをするという構成を取っているためです。

めんどくさい人は コードに直接書いてもいいと思いますが そのコードは公開しないように注意しておきましょう。

以下のコードは`line.env`を環境変数に読み込むための処理です。
読み込みに失敗したらそのまま死にます。

```go:app.go
func init() {
	err := godotenv.Load("line.env")
	if err != nil {
		panic(err)
	}
}
```

次に Handler を設定しておきます。
Webhookを受け取るやつと実際に処理をするためのHandlerを作成します。

```go:app.go
var botHandler *httphandler.WebhookHandler

func init() {
	err := godotenv.Load("line.env")
	if err != nil {
		panic(err)
	}

	botHandler, err = httphandler.New(
		os.Getenv("LINE_BOT_CHANNEL_SECRET"),
		os.Getenv("LINE_BOT_CHANNEL_TOKEN"),
	)
	botHandler.HandleEvents(handleCallback)

	http.Handle("/callback", botHandler)
	http.HandleFunc("/task", handleTask)
}

// Webhook を受け取って TaskQueueに詰める関数
func handleCallback(evs []*linebot.Event, r *http.Request) {
}

// 受け取ったメッセージを処理する関数
func handleTask(w http.ResponseWriter, r *http.Request) {
}
```

次に *linebot.Client を作成するための 関数を作っておきます。
handler内に毎回書いてては長くなってしまうためです。

```go
func newLINEBot(c context.Context) (*linebot.Client, error) {
	return botHandler.NewClient(
		linebot.WithHTTPClient(urlfetch.Client(c)),
	)
}

// newContext は appengine.NewContext を短く書くための関数
func newContext(r *http.Request) context.Context {
	return appengine.NewContext(r)
}

// logf は log.Infof を短く書くための関数
func logf(c context.Context, format string, args ...interface{}) {
	log.Infof(c, format, args...)
}

// errorf は log.Errorf を短く書くための関数
func errorf(c context.Context, format string, args ...interface{}) {
	log.Errorf(c, format, args...)
}
```

特徴としては `linebot.WithHTTPClient(urlfetch.Client(c))` です。
urlfetchのクライアントを使って通信するように設定します。

本当は`newLINEBot`って関数を書きたくはなく`*linebot.Client`は常に一つでリクエストごとにTransportを切り替えたいのですが([issue/15](https://github.com/line/line-bot-sdk-go/issues/15)) とりあえずこういう書き方になります。

そして次は webhook を受け取る Handler を実装します。

まずは関数全体から

```go
func handleCallback(evs []*linebot.Event, r *http.Request) {
	c := newContext(r)
	ts := make([]*taskqueue.Task, len(evs))
	for i, e := range evs {
		j, err := json.Marshal(e)
		if err != nil {
			errorf(c, "json.Marshal: %v", err)
			return
		}
		data := base64.StdEncoding.EncodeToString(j)
		t := taskqueue.NewPOSTTask("/task", url.Values{"data": {data}})
		ts[i] = t
	}
	taskqueue.AddMulti(c, ts, "")
}
```

`linebot/httphandler.WebhookHandler`を使っているので検証をした状態で`[]*linebot.Event`を渡してくれます。 ([Add event handler #17](https://github.com/line/line-bot-sdk-go/pull/17)で取り込まれました)

なのでこの`handler`では`[]*linebot.Event`を一度jsonにシリアライズして PushQueue用のTaskの`data`にセットして、最後に`taskqueue.AddMulti(c, ts, "")`でdefaultのPushQueueにTaskを詰めます。

次に処理側です。

```go
func handleTask(w http.ResponseWriter, r *http.Request) {
	c := newContext(r)
	data := r.FormValue("data")
	if data == "" {
		errorf(c, "No data")
		return
	}

	j, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		errorf(c, "base64 DecodeString: %v", err)
		return
	}

	e := new(linebot.Event)
	err = json.Unmarshal(j, e)
	if err != nil {
		errorf(c, "json.Unmarshal: %v", err)
		return
	}

	bot, err := newLINEBot(c)
	if err != nil {
		errorf(c, "newLINEBot: %v", err)
		return
	}

	logf(c, "EventType: %s\nMessage: %#v", e.Type, e.Message)

	m := linebot.NewTextMessage("ok")
	if _, err = bot.ReplyMessage(e.ReplyToken, m).WithContext(c).Do(); err != nil {
		errorf(c, "ReplayMessage: %v", err)
		return
	}

	w.WriteHeader(200)
}
```

以下の部分でまずデシリアライズしています。

```go
	data := r.FormValue("data")
	if data == "" {
		errorf(c, "No data")
		return
	}

	j, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		errorf(c, "base64 DecodeString: %v", err)
		return
	}

	e := new(linebot.Event)
	err = json.Unmarshal(j, e)
	if err != nil {
		errorf(c, "json.Unmarshal: %v", err)
		return
	}
```

あとは 単純に`ok`を返すだけの処理です。

```go
	m := linebot.NewTextMessage("ok")
	if _, err = bot.ReplyMessage(e.ReplyToken, m).WithContext(c).Do(); err != nil {
		errorf(c, "ReplayMessage: %v", err)
		return
	}
```

# デプロイ

Deployには以下のコマンドでできます。

```
$ goapp deploy
```

![IMG_0689.PNG](https://qiita-image-store.s3.amazonaws.com/0/15818/a669b90c-1976-09b2-ef00-9f13f2fcb4ee.png)


今回のコードは[GitHub](https://github.com/k2wanko/appengine-line-bot/tree/master/simple)にあげてあります。

# まとめ

ちょっとめんどくさく感じるかもしれませんが
大量のメッセージに耐える設計を意識しているためです。

参考: [大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ](http://qiita.com/yoichiro6642/items/6d4c7309210af20a5c8f)

ちなみに新しいSDKがreleaseされた時はGAE/Go SDKでビルドできない問題があったり
TaskQueueに詰めるためにJSONにシリアライズしようとしたら、できなかったりで躓きまくってましたが、なんとか最低限AppEngineで実装できるところまでは行けるようになりました。

次回は Beaconかもうちょい実践的なBotを作りたいと思います。