+++
title = "GolangでLine APIをいじくるよ"
date = 2019-03-03T19:12:22+08:00
description = "GolangでLine APIを利用してみましたよ。"
tags = ["Golang"]
categories = ["プログラミング"]
author = "kazu634"
images = ["ogp/2019-02-23-line-sdk-go.webp"]
+++

# GolangでLine APIをいじくる
`Golang`で`Line` APIを用いたボット作成をしてみます。

## 説明すること・しないこと
この記事ではLine Messaging APIを使って、どんなメッセージを送信できるかを説明します。

どうすればボット作成環境を構築するかなどは説明しません。参考リンクなど確認ください。

## 簡単な説明
下の図のLineBotServerの部分を作り込みます:

{{<mermaid align="center">}}
sequenceDiagram
  User->>Line: Some Action(s)
  Line->>LineBotServer: Make HTTP Request
  alt Bad Request
    Note over LineBotServer: Do nothing
  else Proper Request
    LineBotServer->>LineBotServer: Do something
    LineBotServer->>Line: Make HTTP Request, using REST API
    Line->>User: Show some message(s) as Reply
  end
{{< /mermaid >}}

## Line Developerに登録する
[お金をかけずにLINEのMessaging APIの投稿(push)と返信(replay)を試してみる。 | ポンコツエンジニアのごじゃっぺ開発日記。](https://blog.pnkts.net/2018/06/03/line-messaging-api/)に書いてある通りに進めます。

## とりあえず動作させてみます
送信できるメッセージの種類ごとにテストしてみます。

### テキストメッセージ
“text”とメッセージ送信したら、”text”と返信するようにしてみました。

![text message](https://farm8.staticflickr.com/7891/46350633365_4e30224e64.jpg)

### 画像メッセージ
“image”とメッセージ送信したら、画像を投稿するようにしてみました。

![image message](https://farm8.staticflickr.com/7819/33389330058_05399428bd.jpg)

### スタンプメッセージ
“sticker”とメッセージ送信したら、スタンプを投稿するようにしてみました。送信できるステッカーは[Line Botで送信できるSticker一覧 (PDF)](https://developers.line.biz/media/messaging-api/sticker_list.pdf)から確認できます:

![sticker message](https://farm8.staticflickr.com/7807/40300515403_5993c84e2a.jpg)

### 位置情報メッセージ
“location”とメッセージ送信したら、地図情報を投稿するようにしてみました:

![location message](https://farm8.staticflickr.com/7918/46350633335_f2e7ecda53.jpg)

### テンプレートメッセージ
画像やボタンなどを組み合わせたメッセージです。

#### ボタンテンプレート
こんな感じのメッセージになります:

![button-template message](https://farm8.staticflickr.com/7808/33389330098_48880e1512.jpg)

#### 確認テンプレート
確認を行うダイアログ?的なメッセージになります:

![confirm message](https://farm8.staticflickr.com/7880/40300515423_e471e9de20.jpg)

#### カルーセルテンプレート
横方向にテンプレートメッセージを並べたメッセージです。こんな感じになります:

![carousel message](https://farm8.staticflickr.com/7870/47213297182_fab28aa76e.jpg)

### Flexメッセージ
テンプレートメッセージのレイアウトを自由に調整できるみたいなのですが、サンプルしか触っていないため、ちょっとよくわかっていないです。こんな感じになりました:

![flex message](https://farm8.staticflickr.com/7912/40300515343_910467fd1e.jpg)

もっとレイアウトにこったサンプルがあればいいのですが。。。

### クイックレスポンスメッセージ
メッセージ送信と同時に、簡単な選択肢を提示して、選択してもらうことができるようです:

![quick response message](https://farm8.staticflickr.com/7887/46350633325_0398b4f65c.jpg)

位置情報の送信、カメラ起動、カメラロールから画像選択などもできるみたい。上の例だと、右端のリンクをクリックすると、位置情報の送信画面になります。

### その他 (ボタンをクリックするとできること)
テンプレートメッセージやFlexメッセージでボタンを配置してできることは、以下のようです。

#### Postback action
Botのcallback URLに対して、データをPOSTリクエストで送信するようです。

#### Message action
選択したボタンに割り当てたメッセージをLine上で送信するみたいです。

#### URI action
選択したボタンに割り当てたURLをLineのブラウザで開くようです。

#### Datetime picker action
日付選択用の画面が出てきます。

![date and time picker example](https://farm8.staticflickr.com/7897/33389330108_8d940922b1.jpg)

#### Camera action
クイックレスポンスの時にのみ利用できるアクションのようです。カメラが起動します。

#### Camera roll action
クイックレスポンスの時にのみ利用できるアクションのようです。カメラロールが開いて、画像を選択できるようです。

#### Location action
クイックレスポンスの時にのみ利用できるアクションのようです。位置情報の画面が出てくるようです。

## コード
```go
package main

import (
        "log"
        "net/http"
        "os"
        "time"

        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/mysql"
        "github.com/line/line-bot-sdk-go/linebot"
)

const (
        userStatusAvailable    string = "available"
        userStatusNotAvailable string = "not_available"
)

type user struct {
        Id         string `gorm:"primary_key"`
        IdType     string
        Timestamp  time.Time `gorm:"not null;type:datetime"`
        ReplyToken string
        Status     string
}

func gormConnect() *gorm.DB {
        DBMS := "mysql"
        USER := "root"
        PASS := "Holiday88"
        PROTOCOL := "tcp(192.168.10.200:3307)"
        DBNAME := "LineBot"

        CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?parseTime=true&loc=Asia%2FTokyo"
        db, err := gorm.Open(DBMS, CONNECT)

        if err != nil {
                log.Fatal(err.Error())
        }
        return db
}

func main() {
        bot, err := linebot.New(
                os.Getenv("CHANNEL_SECRET"),
                os.Getenv("CHANNEL_TOKEN"),
        )
        if err != nil {
                log.Fatal(err)
        }

        // Setup HTTP Server for receiving requests from LINE platform
        http.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) {
                events, err := bot.ParseRequest(req)
                if err != nil {
                        if err == linebot.ErrInvalidSignature {
                                w.WriteHeader(400)
                        } else {
                                w.WriteHeader(500)
                        }
                        return
                }

                for _, event := range events {
                        switch event.Type {
                        case linebot.EventTypeFollow:
                                // db instance
                                db := gormConnect()
                                defer db.Close()

                                userData := user{Id: event.Source.UserID,
                                        IdType:     string(event.Source.Type),
                                        Timestamp:  event.Timestamp,
                                        ReplyToken: event.ReplyToken,
                                        Status:     userStatusAvailable,
                                }
                                err := db.Where(user{Id: event.Source.UserID}).Assign(&userData).FirstOrCreate(&user{})
                                log.Println(err)

                        case linebot.EventTypeUnfollow:
                                log.Println("Unfollow Event: " + event.Source.UserID)
                                log.Println(event)

                                // db instance
                                db := gormConnect()
                                defer db.Close()

                                userData := user{Id: event.Source.UserID,
                                        IdType:     string(event.Source.Type),
                                        Timestamp:  event.Timestamp,
                                        ReplyToken: event.ReplyToken,
                                        Status:     userStatusNotAvailable,
                                }

                                err := db.Where(user{Id: event.Source.UserID}).Assign(&userData).FirstOrCreate(&user{})
                                log.Println(err)

                        case linebot.EventTypeMessage:
                                log.Println(event)
                                switch message := event.Message.(type) {
                                case *linebot.TextMessage:
                                        switch message.Text {
                                        case "text":
                                                resp := linebot.NewTextMessage(message.Text)

                                                _, err := bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "sticker":
                                                resp := linebot.NewStickerMessage("3", "230")

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "location":
                                                resp := linebot.NewLocationMessage("現在地", "宮城県多賀城市", 38.297807, 141.031)

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "image":
                                                resp := linebot.NewImageMessage("https://farm5.staticflickr.com/4849/45718165635_328355a940_m.jpg", "https://farm5.staticflickr.com/4849/45718165635_328355a940_m.jpg")

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "buttontemplate":
                                                resp := linebot.NewTemplateMessage(
                                                        "this is a buttons template",
                                                        linebot.NewButtonsTemplate(
                                                                "https://farm5.staticflickr.com/4849/45718165635_328355a940_m.jpg",
                                                                "Menu",
                                                                "Please select",
                                                                linebot.NewPostbackAction("Buy", "action=buy&itemid=123", "", "displayText"),
                                                                linebot.NewPostbackAction("Buy", "action=buy&itemid=123", "text", ""),
                                                                linebot.NewURIAction("View detail", "http://example.com/page/123"),
                                                        ),
                                                )

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "datetimepicker":
                                                resp := linebot.NewTemplateMessage(
                                                        "this is a buttons template",
                                                        linebot.NewButtonsTemplate(
                                                                "https://farm5.staticflickr.com/4849/45718165635_328355a940_m.jpg",
                                                                "Menu",
                                                                "Please select a date,  time or datetime",
                                                                linebot.NewDatetimePickerAction("Date", "action=sel&only=date", "date", "2017-09-01", "2017-09-03", ""),
                                                                linebot.NewDatetimePickerAction("Time", "action=sel&only=time", "time", "", "23:59", "00:00"),
                                                                linebot.NewDatetimePickerAction("DateTime", "action=sel", "datetime", "2017-09-01T12:00", "", ""),
                                                        ),
                                                )

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "confirm":
                                                resp := linebot.NewTemplateMessage(
                                                        "this is a confirm template",
                                                        linebot.NewConfirmTemplate(
                                                                "Are you sure?",
                                                                linebot.NewMessageAction("Yes", "yes"),
                                                                linebot.NewMessageAction("No", "no"),
                                                        ),
                                                )

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "carousel":
                                                resp := linebot.NewTemplateMessage(
                                                        "this is a carousel template with imageAspectRatio,  imageSize and imageBackgroundColor",
                                                        linebot.NewCarouselTemplate(
                                                                linebot.NewCarouselColumn(
                                                                        "https://farm5.staticflickr.com/4849/45718165635_328355a940_m.jpg",
                                                                        "this is menu",
                                                                        "description",
                                                                        linebot.NewPostbackAction("Buy", "action=buy&itemid=111", "", ""),
                                                                        linebot.NewPostbackAction("Add to cart", "action=add&itemid=111", "", ""),
                                                                        linebot.NewURIAction("View detail", "http://example.com/page/111"),
                                                                ).WithImageOptions("#FFFFFF"),
                                                                linebot.NewCarouselColumn(
                                                                        "https://farm5.staticflickr.com/4849/45718165635_328355a940_m.jpg",
                                                                        "this is menu",
                                                                        "description",
                                                                        linebot.NewPostbackAction("Buy", "action=buy&itemid=111", "", ""),
                                                                        linebot.NewPostbackAction("Add to cart", "action=add&itemid=111", "", ""),
                                                                        linebot.NewURIAction("View detail", "http://example.com/page/111"),
                                                                ).WithImageOptions("#FFFFFF"),
                                                        ).WithImageOptions("rectangle", "cover"),
                                                )
                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "flex":
                                                resp := linebot.NewFlexMessage(
                                                        "this is a flex message",
                                                        &linebot.BubbleContainer{
                                                                Type: linebot.FlexContainerTypeBubble,
                                                                Body: &linebot.BoxComponent{
                                                                        Type:   linebot.FlexComponentTypeBox,
                                                                        Layout: linebot.FlexBoxLayoutTypeVertical,
                                                                        Contents: []linebot.FlexComponent{
                                                                                &linebot.TextComponent{
                                                                                        Type: linebot.FlexComponentTypeText,
                                                                                        Text: "hello",
                                                                                },
                                                                                &linebot.TextComponent{
                                                                                        Type: linebot.FlexComponentTypeText,
                                                                                        Text: "world",
                                                                                },
                                                                        },
                                                                },
                                                        },
                                                )

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        case "quickresponse":
                                                resp := linebot.NewTextMessage(
                                                        "Select your favorite food category or send me your location!",
                                                ).WithQuickReplies(
                                                        linebot.NewQuickReplyItems(
                                                                linebot.NewQuickReplyButton("https://example.com/sushi.png", linebot.NewMessageAction("Sushi", "Sushi")),
                                                                linebot.NewQuickReplyButton("https://example.com/tempura.png", linebot.NewMessageAction("Tempura", "Tempura")),
                                                                linebot.NewQuickReplyButton("", linebot.NewLocationAction("Send location")),
                                                        ),
                                                )

                                                _, err = bot.ReplyMessage(event.ReplyToken, resp).Do()
                                                if err != nil {
                                                        log.Print(err)
                                                }
                                        }
                                }
                        }
                }
        })

        if err := http.ListenAndServe(":"+os.Getenv("PORT"), nil); err != nil {
                log.Fatal(err)
        }
}
```

## 参考
- [お金をかけずにLINEのMessaging APIの投稿(push)と返信(replay)を試してみる。 | ポンコツエンジニアのごじゃっぺ開発日記。](https://blog.pnkts.net/2018/06/03/line-messaging-api/)
- [Line Botで送信できるSticker一覧 (PDF)](https://developers.line.biz/media/messaging-api/sticker_list.pdf)
- [LINE Messaging APIのテンプレートメッセージをまとめてみる | DevelopersIO](https://dev.classmethod.jp/etc/line-messaging-api/)
- [LINE Messaging APIのテンプレートメッセージをまとめてみる(アクションオブジェクト編) | DevelopersIO](https://dev.classmethod.jp/etc/line-messaging-api-action-object/)
- [LINE Messaging APIとは?~Flex Messageやクイックリプライ等の新機能追加でより柔軟なメッセージ配信が可能に - Feedmatic Blog](https://blog.feedmatic.net/entry/LINE/20180809/about-Messaging-API)
- [Using quick replies](https://developers.line.biz/en/docs/messaging-api/using-quick-reply/)
- [毎朝 天気を通知する LINE Bot を作ってみました。 - hawk, camphora, avocado and so on..](http://takagusu.hateblo.jp/entry/2017/01/24/200453)
- [【LINE Botの作り方】Python × Messaging APIでプッシュ通知を行うボットを作ろう | TAKEIHO](https://www.takeiho.com/line-bot-push)
- [LINE Messaging API を使ってLINEにメッセージ送信/メッセージ返信する - Qiita](https://qiita.com/fkooo/items/d07a7b717e120e661093)