blog/content/post/2019/02/23/2019-02-23-line-sdk-go.md

390 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

+++
title = "GolangでLine APIをいじくるよ"
date = 2019-03-03T19:12:22+08:00
Description = "GolangでLine APIを利用してみましたよ。"
Tags = []
Categories = ["golang", "programming"]
url = "/2019/03/03/line-sdk-go/"
image="https://farm9.staticflickr.com/8462/29581774242_bf25a0a820_z.jpg"
+++
# GolangでLine APIをいじくる
`Golang`で`Line` APIを用いたボット作成をしてみます。
## 説明すること・しないこと
この記事ではLine Messaging APIを使って、どんなメッセージを送信できるかを説明します。
どうすればボット作成環境を構築するかなどは説明しません。参考リンクなど確認ください。
## 簡単な説明
下の図のLineBotServerの部分を作り込みます:
<div class="mermaid">
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
</div>
## 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
クイックレスポンスの時にのみ利用できるアクションのようです。位置情報の画面が出てくるようです。
## コード
```
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)