LoginSignup
68

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-10-04

screenshot_.png

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

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

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

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

事前準備

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

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

Cloud Consoleで新しいプロジェクトを作成してprojectIDをメモっておきましょう。

登録

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

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

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

スクリーンショット 2016-09-29 14.17.20.png

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

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

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

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

simple Bot

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

SDKのインストール

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

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

$ 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を作成します。

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だったら以下のようになります。

application: linebot-example

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

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

実装

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

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

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

line.env
LINE_BOT_CHANNEL_SECRET=<ChannelSecret>
LINE_BOT_CHANNEL_TOKEN=<AccessToken>

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

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

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

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

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

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内に毎回書いてては長くなってしまうためです。

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) とりあえずこういう書き方になります。

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

まずは関数全体から

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で取り込まれました)

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

次に処理側です。

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)
}

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

    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を返すだけの処理です。

    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

今回のコードはGitHubにあげてあります。

まとめ

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

参考: 大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
68