+++ title = "GolangでLine APIをいじくるよ" date = 2019-03-03T19:12:22+08:00 description = "GolangでLine APIを利用してみましたよ。" tags = ["Golang"] categories = ["プログラミング"] author = "kazu634" +++ # GolangでLine APIをいじくる `Golang`で`Line` APIを用いたボット作成をしてみます。 ## 説明すること・しないこと この記事ではLine Messaging APIを使って、どんなメッセージを送信できるかを説明します。 どうすればボット作成環境を構築するかなどは説明しません。参考リンクなど確認ください。 ## 簡単な説明 下の図のLineBotServerの部分を作り込みます: {{}} 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)