LoginSignup
39
41

More than 5 years have passed since last update.

画像を送ると何が写っているのかを返す LINE Bot を GCP で作った話

Last updated at Posted at 2017-05-16

こんにちは @wezardnet です。
今年も Google I/O (2017) の時期がやってきました。残念ながら健康上の理由により参加を見送りましたが(チケット当選してないけど :sweat:)、また機会があれば参加してみたいです。

さて、今回は Google Cloud Vision API & Google Cloud Translation API を使って LINE で画像を送ると、その画像に何が写っているかを返す bot を作ってみました。プラットフォームは GAE/Go です :sunglasses:

QiitaでTwitterのツイートを埋め込める時代は終わりました という事なのでサンプルアプリのスクショを貼り付けることにします ^^;

1. LINE bot を作る準備

はじめに LINE bot を作るには LINE BUSINESS CENTER で登録した後 LINE Developers で Messaging API を使えるようにします。このあたりの事前準備は他にたくさん解説記事がありますので、今さら説明するまでもありません。特に以下の記事を読むと良いでしょう :point_up:

2. 中身(コード)について

LINE bot には SDK が用意されているので、こちらを使うと Messaging API を直に使うより楽ちんです。今回はプラットフォームに GAE/Go を使うので Go 用の line-bot-sdk-go を使います。ざっと見た感じ Go 以外には以下の言語の LINE bot SDK があるみたいです。

まずは必要なパッケージをインポートします。尚、コードは見づらくなるので一部省略してます。
ちなみにパッケージ管理は glide を使ってます。また Cloud Vision API と Cloud Translation API は独自のヘルパーパッケージにまとめてます。

Import
import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "os"

    "bitbucket.org/{my project}/lib/helper"

    "github.com/joho/godotenv"
    "github.com/line/line-bot-sdk-go/linebot"
    "github.com/zenazn/goji"

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

私はウェブフレームワークに zenazn/goji を使っているので、ルーティングは次のようになります。

init
func init() {
    m := goji.DefaultMux
    m.Post("/line/callback", lineCallbackHandler)
}

LINE bot 側のメッセージを受け取る(フックする) URL を LINE Developers の Channel 設定画面で登録してあげます。

LINE developers.png

HTTP ハンドラ部分はおおむね line-bot-sdk-goExample を真似して実装すれば OK です。注意点としては GAE を使う場合、外部 API を叩く場合は urlfetch を使わなければならない点でしょうか…

lineCallbackHandler
func lineCallbackHandler(c web.C, w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r)

    // bot 用の設定ファイルを読み込む
    err := godotenv.Load("/line.env")
    if err != nil {
        log.Errorf(ctx, "LINE bot Setting file read error.: %v", err)
        return
    }

    bot, err := linebot.New(
        os.Getenv("LINE_BOT_CHANNEL_SECRET"), 
        os.Getenv("LINE_BOT_CHANNEL_TOKEN"), 
        linebot.WithHTTPClient(urlfetch.Client(ctx)), 
    )
    if err != nil {
        log.Errorf(ctx, "LINE bot new error.: %v", err)
        return
    }

    // リクエストを受信する
    events, err := bot.ParseRequest(r)
    if err != nil {
        if err == linebot.ErrInvalidSignature {
            w.WriteHeader(http.StatusBadRequest)
        } else {
            w.WriteHeader(http.StatusInternalServerError)
        }
        return
    }

    for _, event := range events {
        if event.Type == linebot.EventTypeMessage {
            var replyMsg string = ""
            switch message := event.Message.(type) {
                case *linebot.TextMessage:
                    log.Infof(ctx, fmt.Sprintf("message.Text = %s", message.Text))
                    replyMsg = fmt.Sprintf("「%s」じゃねーよ。画像送ってくれよぉぉー", message.Text)

                case *linebot.ImageMessage:
                    // 送信された画像を取得する
                    mc, err := bot.GetMessageContent(message.ID).Do()
                    if err != nil {
                        log.Errorf(ctx, "LINE bot get content error.: %v", err)
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                    }
                    data, _ := ioutil.ReadAll(mc.Content)

                    vision, err := helper.NewVision(ctx, r)
                    if err != nil {
                        log.Errorf(ctx, "fatal new vision error.: %v", err)
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                    }

                    // Cloud Vision API で画像を解析させる
                    ret, err := vision.AnnotateImage(data)
                    if err != nil {
                        log.Errorf(ctx, "fatal annotate image error.: %v", err)
                        w.WriteHeader(http.StatusInternalServerError)
                        return
                    }

                    translate, _ := helper.NewTranslate(ctx, r)

                    responses := ret.Get("responses")
                    for i := 0; i < len(responses.MustArray()); i++ {
                        annotations := responses.GetIndex(i).Get("labelAnnotations")
                        for j := 0; j < len(annotations.MustArray()); j++ {
                            annotation := annotations.GetIndex(j)
                            jdescription, _ := translate.TranslateEn2Ja(annotation.Get("description").MustString())
                            replyMsg += "お前が送ってきた画像だけど、『" + jdescription + "』だろ。おれの画像認識力を甘く見ちゃダメだぜw"
                            break
                        }

                        if replyMsg == "" {
                            replyMsg += "なんだこれ? (´・ω・`)"
                        }
                        break
                    }
            }
            if replyMsg != "" {
                if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(replyMsg)).Do(); err != nil {
                    log.Errorf(ctx, "LINE bot Reply Message error.: %v", err)
                }
            }
        }
    }
}

Cloud Vision API で画像識別させた結果は複数の候補が返ってきますが、一番スコアが高いモノを採用して返すようにしてます。
また、テキストメッセージを受信した場合は、↓のような感じで画像を送ってもらうように催促しましょうw

IMG_0702.png

3. あとがき

今回初めて LINE bot を作ったんですけど、サンプルが多いので意外と手軽に作れますね。実用的な bot を作るにはアイデア次第な気がします。ちなみに他にはどんな bot が作られているかは以下の記事を読むと良いでしょう。みなさん、おもしろい bot 作ってますね!

39
41
0

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
39
41