Golang + Gin + Heroku でLineBotを作ってみた

はじめに

コロナ禍大変になってきてますね。
最近かなり暖かくもなってきたので、誰にも迷惑をかけず感染リスクも侵さない娯楽としてバイクにめちゃくちゃ乗りたくなってきたのですが、
残念ながらまだかつての愛車も修理が終わっておらず、新たに購入する資金力も無いため、家族共々自宅に籠もっております。
ふと思い立ってGoを勉強してみたいと思い、勉強し始めました。特に理由はありません。
基本的な文法を学ぶ必要がありますが、モチベーション維持のためにはやはり何か作りながらのほうがいい。
しかし特に作りたいものも無いしどうしたものか。。

現在、妻へ連絡する色々を自動化するためにLineBotを使っているのですが、
バックエンドは昔PHPで実装したものをそのまま継続的に使用しており(学生時代PHPしか書けなかったので。。。)
最近個人的にPHPを使うことも少なくなってきたので、Goで書き直そうとやってみました。
取り合えずSDKとGoのWebフレームワークであるGinを使ってオウム返答するところまでを動かします。

Goの環境をDockerで構築

Flask-AskでAlexaのカスタムスキルを作成 - 雑食へいちゃんの思い出達

この記事でやったことと同じです。
まずはこんなalpineベースのgolangイメージをベースにDockerfileを作成します。

FROM golang:1.14.2-alpine3.11

WORKDIR /go
RUN apk update && \
    apk add vim git

以下コマンドでコンテナの中に入ります。

$ docker build . -t go-env
$ docker run -it -v $PWD:/go -p 8080:8080 go-env /bin/ash

コンテナに入ったらGinをインストールします。

$ go get github.com/gin-gonic/gin

まずは、サンプルのmain.goを書いて動作を確認します。

package main

import (
  "net/http"
  "github.com/gin-gonic/gin"
)
func main() {
  router := gin.Default()
  router.GET("/hello", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello World!!")
  })
  router.Run(":8080")
}
$ go run main.go

ブラウザからlocalhost:8080/helloにアクセスして「Hello World!!」と表示されれば成功です。

次はこれをHerokuにデプロイしていきます。
Dockerのコンテナに作った環境をherokuにデプロイしていくので、
Dockerfileを以下の通り書き換えます。

FROM golang:1.14.2-alpine3.11

WORKDIR /go
ADD . /go
RUN apk update && \
    apk add git vim
CMD go run main.go

また、main.goで立ち上げるポートも8080ではなく、環境変数を使用するように変更します。

package main

import (
  "os"
  "net/http"
  "github.com/gin-gonic/gin"
)
func main() {
  bot, err := linebot.New(
    os.Getenv("LINEBOT_CHANNEL_SECRET"),
    os.Getenv("LINEBOT_CHANNEL_TOKEN"),
  )
  if err != nil {
    log.Fatal(err)
  }
  router := gin.Default()

  router.GET("/hello", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello World!!")
  })
  router.Run(":" + os.Getenv("PORT")) // ここを環境変数を参照するように変更
}

ローカル環境のDockerコンテナ内でGinサーバを立ち上げる際は、以下コマンドで環境変数を追加する必要があります。

$ export PORT=8080

続いてHerokuにデプロイしていきます。
デプロイの方法は先程同様↓の記事を参照ください。

Flask-AskでAlexaのカスタムスキルを作成 - 雑食へいちゃんの思い出達

HerokuのURLにアクセスし、localhostと同様の表示が出れば成功です。

オウム返しするLineBotを作成する

Line Messaging APIにはGolang用のSDKが提供されているので、それを使用します。

github.com

$ go get github.com/line/line-bot-sdk-go/linebot

main.goを以下のように修正します。
基本すべて公式に掲載されているサンプルですが、Ginに合わせて一部修正しています。

line-bot-sdk-go/server.go at master · line/line-bot-sdk-go · GitHub

package main

import (
  "os"
  "fmt"
  "log"
  "net/http"
  "github.com/gin-gonic/gin"
  "github.com/line/line-bot-sdk-go/linebot"
)
func main() {
  bot, err := linebot.New(
    os.Getenv("LINEBOT_CHANNEL_SECRET"),
    os.Getenv("LINEBOT_CHANNEL_TOKEN"),
  )
  if err != nil {
    log.Fatal(err)
  }
  router := gin.Default()

  router.GET("/hello", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello World!!")
  })

  router.POST("/callback", func(c *gin.Context) {
    events, err := bot.ParseRequest(c.Request)
    if err != nil {
      if err == linebot.ErrInvalidSignature {
        c.Writer.WriteHeader(400)
      } else {
        c.Writer.WriteHeader(500)
      }
      return
    }
    for _, event := range events {
      if event.Type == linebot.EventTypeMessage {
        switch message := event.Message.(type) {
          case *linebot.TextMessage:
            if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(message.Text)).Do(); err != nil {
              log.Print(err)
            }
          case *linebot.StickerMessage:
            replyMessage := fmt.Sprintf(
              "sticker id is %s, stickerResourceType is %s", message.StickerID, message.StickerResourceType)
            if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(replyMessage)).Do(); err != nil {
              log.Print(err)
            }
        }
      }
    }
  })
  router.Run(":" + os.Getenv("PORT"))
}

これをHerokuにデプロイし、LINEBOT_CHANNEL_SECRETLINEBOT_CHANNEL_TOKEN環境変数として設定し、Line Developersサイトでcallback URLにHerokuのサイトURLを設定すれば完了です。

終わり

とりあえず動くところまではできたので、文法についても勉強しながら、現行で動いている機能を移植していきたいと思います。