GAE/Go (echoフレームワーク)で Line Message API 使って Bot を作る。

Line Message API を使って、おうむ返しするだけのBotを作ってみました。

# 準備
まず、Line Message API を使用するための準備を行います。
以下の記事を参考にしました。  
[LINE BOTの作り方を世界一わかりやすく解説（１）【アカウント準備編】](http://qiita.com/carat_yoshizaki/items/bd4277d3943200beab26#_reference-332a86e9751c93ad1f89)

また、GAE/Goのデプロイできる環境は[公式ドキュメント](https://cloud.google.com/appengine/docs/go/)か[こちらの記事](http://www.apps-gcp.com/gae-go-gettingstart-01/)などを参考に構築してください。  

今回はGolangのフレームワークとして[echo](https://github.com/labstack/echo)を使用しました。

```
$ go get -u github.com/labstack/echo
```

# 実装
GAEでechoを動かす方法は[チュートリアル](https://echo.labstack.com/recipes/google-app-engine)があるので、それを見ながら実装します。

実装にあたってはこちらの記事も参考にさせていただきました。
[Go製のフレームワークechoを使ってJSONを返すWebサーバーを作り、GoogleAppEngineで動かす](http://qiita.com/qube81/items/701279c43b33ce923613)

まず、起点となるファイル。

```go:app.go
package main

var e = createMux()
```

次にローカルのdev server用の実装。
※１行目のコメントを書き漏らさないようにしてください。

```go:app-standalone.go
// +build !appengine,!appenginevm

package main

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
)

func createMux() *echo.Echo {
    e := echo.New()

    e.Use(middleware.Recover())
    e.Use(middleware.Logger())
    e.Use(middleware.Gzip())

    e.Use(middleware.Static("public"))

    return e
}

func main() {
    e.Run(standard.New(":8080"))
}
```
 

で、デプロイしてGAE/SEで動かすとき用の実装。

```go:app-engine.go
// +build appengine

package main

import (
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"net/http"
)

func createMux() *echo.Echo {
    e := echo.New()

    s := standard.New("")
    s.SetHandler(e)
    http.Handle("/", s)

    return e
}
```

静的なHTMLを作ります。
publicディレクトリを作って、その下に作ることにします。

````html:public/index.html
<h1>Hello, LineBot!</h1>
````

※挙動には関係ありませんが、ちゃんとデプロイできたかの確認にも使えるので、作っておくと良いと思います。  

次は設定ファイルです。
Your Project ID には GCPのプロジェクトIDを入れてください。

````yaml:app.yaml
application: <Your Project ID>
module: default
version: alpha
runtime: go
api_version: go1

handlers:
- url: /
  mime_type: text/html
  static_files: public/index.html
  upload: public/index.html

- url: /.*
  script: _go_app
````

やっとメインの実装です。

````go:hook.go
package main

import (
    "math/rand"
    "net/http"
    "strings"
    "time"

    "github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
	"github.com/line/line-bot-sdk-go/linebot"
	"google.golang.org/appengine"
    "google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

func init() {

    g := e.Group("/hook")
    g.Use(middleware.CORS())

    g.GET("", helloMsg)
    g.POST("", postBot)
}

func helloMsg(c echo.Context) error {
    // 意味ないんですが、一応GETの返却
    return c.JSON(http.StatusOK, "hello!")
}

func postBot(c echo.Context) error {
    // SecretとTokenはLineの設定画面にあります
    channelSecret := "<Your Channel Secret>"
    channelAccessToken := "<Your Channel AccessToken>"

    // appengineのコンテキストからclientを生成します。ここがポイント。
    cx := appengine.NewContext(c.Request().(*standard.Request).Request)
    client := urlfetch.Client(cx)

    bot, err := linebot.New(channelSecret, channelAccessToken, linebot.WithHTTPClient(client))
    if err != nil {
        log.Infof(cx, err.Error())
        return c.JSON(http.StatusInternalServerError, err)
    }

    received, err := bot.ParseRequest(c.Request().(*standard.Request).Request)
    if err != nil {
        log.Infof(cx, err.Error())
        return c.JSON(http.StatusInternalServerError, err)
    }

    for _, event := range received {
        if event.Type == linebot.EventTypeMessage {
            switch message := event.Message.(type) {
            case *linebot.TextMessage:
                log.Infof(cx, "TextMessage %#v", message)
                // 送ったメッセージをおうむ返ししているだけ
                resMessage := linebot.NewTextMessage(message.Text)
                if _, err = bot.ReplyMessage(event.ReplyToken, resMessage).Do(); err != nil {
                    log.Errorf(cx, "send error: %v", err)
                }
            }
        }
    }

    return c.JSON(http.StatusOK, "success")
}
````

ファイル構成

    ├─ app.go
    ├─ app.yaml
    ├─ app-engine.go
    ├─ app-standalone.go
    ├─ hook.go
    ├─ public
        ├─index.html

# デプロイ
あとはデプロイして完成です。
デプロイが終わったら、LineでBotに対してメッセージを送ってみてください。

```
goapp deploy ./
```