LoginSignup
6
9

More than 5 years have passed since last update.

GAEにgolangでlinebotをデプロイする

Last updated at Posted at 2016-12-02

背景

以下を参考にさせてもらいました。
LINE Developer Trialを GAE/Go で始める #linedevday

ソース

fk2000/linebot-gae-golang

GAE環境の準備

  1. GAE/Go SDKをダウンロードする
  2. Cloud Consoleで新しいプロジェクトを作成してprojectIDをメモする

LINE Developers設定

MessageAPIを始める

  1. LINE BUSINESS CENTERでMessageAPIを始める
  2. Bot設定から[APIを利用する]ボタンを押して有効したのち、bot設定を以下のように設定する
    項目 設定
    Webhook送信 利用する
    Botのグループトーク参加 利用する
    自動応答メッセージ 利用しない(デフォルトは利用する)
    友だち追加時あいさつ 利用する
  3. LINE Developersで「Channel Secret」と「Channel Access Token」をメモする1
  4. 同じ画面の下の方に、EDITとあるので編集画面に行きWebhookにhttps://<projectID>.appspot.com/callbackと設定する

SDKをインストール

go getでインストール

$ 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

GAEにdeployするための、app.yamlを作成

$ mkdir linebot;cd linebot
$ vi app.yaml

<projectID>にprojectIDを入力してください。

app.yaml
python:
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

line.envを作成する

「Channel Secret」と「Channel Access Token」を環境変数として入力します。

このファイルの取り扱いには注意が必要です。ソースコードをgithubで保管する場合は.gitignoreに/line.envと記入してpushされないようにしてください。

line.env
LINE_BOT_CHANNEL_SECRET=[Your secret]
LINE_BOT_CHANNEL_TOKEN=[Your Channel Token]

app.goを作成する

LINEでテキストを送信すると「ok」と返すだけのコードです。
※ソースコードの解説は省きます。

app.go
package main

import (
    "encoding/base64"
    "encoding/json"
    "net/http"
    "net/url"
    "os"

    "google.golang.org/appengine"
    "google.golang.org/appengine/log"
    "google.golang.org/appengine/taskqueue"
    "google.golang.org/appengine/urlfetch"

    "golang.org/x/net/context"

    "github.com/joho/godotenv"
    "github.com/line/line-bot-sdk-go/linebot"
    "github.com/line/line-bot-sdk-go/linebot/httphandler"
)

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

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

// Webhook を受け取って TaskQueueに詰める関数
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, "")
}

// 受け取ったメッセージを処理する関数
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)
}

デプロイする

$ goapp deploy

もしここで、

ERROR appcfg.py:2411 An error occurred processing file '': HTTP Error 403: Forbidden Unexpected HTTP status 403. Aborting.

と出たら、ホームディレクトリに.appcfg_oauth2_tokensが生成されているためなので、このファイルを削除してdeployを再実行してください。うまくいくとブラウザが開き、googleアカウントでログインする画面が表示されます。

ログインするとデプロイ処理が進みます。Compile failedが出る場合はソースコードに誤りがあります。直して再デプロイしてください。Deployment successful.と表示されたら成功です。

GCP Consoleでロギングを確認。

スクリーンショット 2016-10-18 13.04.00.png

「!!」アイコンの行を展開すると、最下段に

ReplayMessage: linebot: APIError 403 Access to this API denied due to the following reason: Your ip address [Your IP address] is not allowed to access this API. Please add your IP to the IP whitelist in the developer center.

と表示されているのがわかります。

これはLINE DevelopersのServer IP Whitelistに[Your IP address]が登録されていないために、GCPからLINE APIの使用が許可されていないことを表すエラーメッセージとなります。

IP AddressをServer IP Whitelistに登録

10月18日時点の状態です。現在は制限が無く、IP Whitelistへの追加は不要かもしれません。

LINE DevelopersのServer IP WhitelistにGCPで確認したIP Addressを追加します。2

line_dev.png

LINEで友達登録しテキスト送信

スクリーンショット 2016-10-18 13.16.56.png


  1. Messaging APIになってから、「Channel ID」と「MID」は使用しなくなりました。 

  2. IP Addressは複数あるので/24か/30のレンジで登録します。 

6
9
2

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
6
9