えいのうにっき

a-knowの日記です

Pixela をさらに手軽に使える LINE bot をつくりました!

Pixela とは、体重やジョギングした距離など、数値であればどんなものでも記録・その結果を可視化できるWebサービスです。

pixe.la

今回、その Pixela をより手軽に利用することができる LINE bot をつくりました!こんな↓かんじです。

f:id:a-know:20200823172100p:plainf:id:a-know:20200823172109p:plainf:id:a-know:20200823172118p:plain
f:id:a-know:20200823172147p:plainf:id:a-know:20200823172155p:plain
f:id:a-know:20200823172233p:plainf:id:a-know:20200823172242p:plainf:id:a-know:20200823172253p:plain

これを使うためには、LINEに Pixela bot を友だち登録してもらうだけで大丈夫です!QRコードは↓こちら。

f:id:a-know:20200823172321p:plain
このQRコードをLINEアプリで読み込んで友だち登録!

あとは、細かい使い方とかは説明しなくても大丈夫、自然に使ってもらえるはず......。

これで Pixela がより一層、身近で手軽なものになりました!「なんか難しそう」「いちいちコマンド実行するのも面倒くさいな......」と二の足を踏んでいた方、ぜひ使ってみてください!

LINE bot の開発について

正確には、「Messaging API を用いてユーザーから送信されたメッセージを処理して返信メッセージを送信する仕組み」、というかんじかと思います。今回これが初めてだったのですが、こうしてbotの存在を公開するために取り組んだことで得た気付きとか感想とか雑多に列挙していってみます。

SDKのおかげでめちゃくちゃ簡単に開発できる

developers.line.biz

SDKを使っての開発は、めちゃくちゃ簡単だし安心(署名の検証とかもSDKにまかせられる)でした。今回僕はGoで書いたものを Google App Engine でホストしてるのですが、まさにそれを意図したサンプルの実装もGitHubに用意されていました。

func init() {
    handler, err := httphandler.New(
        os.Getenv("CHANNEL_SECRET"),
        os.Getenv("CHANNEL_TOKEN"),
    )
    if err != nil {
        log.Fatal(err)
    }

    // Setup HTTP Server for receiving requests from LINE platform
    handler.HandleEvents(func(events []*linebot.Event, r *http.Request) {
        ctx := appengine.NewContext(r)
        bot, err := handler.NewClient(linebot.WithHTTPClient(urlfetch.Client(ctx)))
        if err != nil {
            aelog.Errorf(ctx, "%v", err)
            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)).WithContext(ctx).Do(); err != nil {
                        aelog.Errorf(ctx, "%v", err)
                    }
                }
            }
        }
    })
    http.Handle("/callback", handler)
}

https://github.com/line/line-bot-sdk-go/blob/master/examples/echo_bot_handler/server_appengine.go より)

実際僕も、この通りのコードをまずデプロイしてちゃんと動くことを確かめて、これに徐々に肉付けしていきながら完成までこぎつけました。とても楽しかったです。その前に必要なアカウント登録などの前準備についても、公式のドキュメントでしっかり解説されているので安心。(こことかこことか)

ドキュメントも素晴らしい

developers.line.biz

開発途中、何度かつまづいたことはありましたが、そのどれも、ドキュメントを再確認することですぐに解決することができました。記述そのものが非常にわかりやすい、ということもあるんですが、他にも例えば、ある対象の記述を読んでいて、それに関連するドキュメントへの参照がしやすい、といったことが、あたりまえのようではあるんですがとても助かりました。

数週間前に以下のような記事をちらっと見かけていて(このときはまだ自分が LINE bot を作ることになるとは全く想像してなかったのですが笑)、そのときは「は〜〜すごいな〜〜たしかにな〜〜」とか思ってたんですが、まさにそれがいかに開発者体験に効いてくるかというのをこの身を持って実感した次第です。

engineering.linecorp.com

自分も仕事でヘルプページやAPIドキュメントを書くこともあるので、今回の体験はぜひ業務にも活かしたいなと思いました。

セッション(的なもの)の管理はちょっと大変

サンプルに対して肉付けしながら様子を把握していった際に気がついたのが、「あれ、これセッション管理的なやつってどうすればいいんだ?」ということ。

少し検索してみたりしたんですが、やっぱり基本的には自分で頑張る必要がある様子(たぶん。何か楽な方法を知っている方はぜひ教えて下さい!)。以下のようなリポジトリも見つけました。僕も最終的にはこちらとだいたいこんなかんじになりました。

github.com

これから作ろうとしているものがどういう状態を取り得るのか、といったところの把握も必要だったので、miroを使って以下のような図を作ってから、本格的な開発に着手しました。(miroも今回初めての利用だったのですが、これも非常に体験がよかったです)

f:id:a-know:20200823170333p:plain

f:id:a-know:20200823170345p:plain

具体的なコツみたいなもの

以下は、LINE bot を Go で実装しながら体得したコツみたいなものをまとめておきます。

ユーザーからの全角文字入力を半角文字へ変換する・末尾の改行を落とす(chompする)

スマートフォンから数値の入力をしてもらう、といったことが主になるので、全角の数字が送信されることもあるだろうなということは想定しました。全角だったら弾く、みたいなことももちろんできるんですがそれだとちょっと体験悪いなということで、ユーザーからの全角文字入力は半角文字へ変換した上で取り扱うようにしました。

それにあたって、ktnyt/go-moji を利用させてもらいました。以下はその紹介記事です。

qiita.com

また、手が滑って末尾に改行を入れてしまうこともあるであろう、ということで、それを落とす(chomp的なことをする)というコードも入れておきました。

定形のユーザー入力はクイックリプライや日時選択アクションで送信できるようにする

クイックリプライとは、こういう↓やつです。

f:id:a-know:20200823172934p:plain

クイックリプライ」というのは、メッセージタイプのうちのひとつになります。「〜〜しますか? Y / N」みたいなものは、YN の文字を入力して送信してもらうよりも、1タップで済むほうがいいよねーということで、今回の bot でもあちこちで利用しています。Pixela LINE bot の実際のコードだと、以下のような感じで簡単に作成できます。

choice := linebot.NewTextMessage("利用規約に同意する場合は「Y」を、そうでない場合は「N」を送信してください。").WithQuickReplies(
    linebot.NewQuickReplyItems(
        linebot.NewQuickReplyButton("", linebot.NewMessageAction("Y: 同意します", "Y")),
        linebot.NewQuickReplyButton("", linebot.NewMessageAction("N: 同意しません", "N")),
    ))
if _, err = bot.ReplyMessage(event.ReplyToken, choice).Do(); err != nil {
    log.Print(err)
}

簡単なのはSDKのおかげ。

あと Pixela LINE bot には日付を入力してもらう必要のある場面もあるのですが、

f:id:a-know:20200823173004p:plain

これも「日時選択アクション」っていういいやつがあって、以下のようなかんじのコードを書くことで、

choice := linebot.NewTextMessage("数値データを登録する日付を選択してください。").WithQuickReplies(
    linebot.NewQuickReplyItems(
        linebot.NewQuickReplyButton("", linebot.NewDatetimePickerAction("日付を選択する", "PostPixel", "date", "", "", "")),
    ))
if _, err = bot.ReplyMessage(event.ReplyToken, choice).Do(); err != nil {
    log.Print(err)
}

以下のようなカレンダーUIが表示されます。

f:id:a-know:20200823172719p:plain

この機能でユーザーから送信されたイベントは Postback タイプになるので、その点だけ要注意かな(もちろんここらへんもドキュメントに助けられまくりました)。

日付なんか、フリーで入力してもらおうとするとフォーマットのばらつきが気になるから、このアクションはとても助かりました。

画像を送信することも可能

ユーザーがメニューで選択したら、現在のグラフ画像をbotが返してくれる、という機能を作りたかったんですが、それもなんの問題もなく対応できました。

imageMessage := linebot.NewImageMessage(fullGraphURL, shortGraphURL)
if _, err = bot.ReplyMessage(event.ReplyToken, imageMessage).Do(); err != nil {
    log.Print(err)
}

ドキュメントはこちら。ダメ元で試してみたけど、SVG形式はやっぱりダメでした。笑

エラー処理が難しい

ここまでに載せているコード片にも含まれているのだけど、メッセージの生成でもエラーハンドリングをする必要があって、うーんこれはどうすればいいんだろうな、と。なにかがおかしいからユーザーにはもう一度リトライしてほしいな、ということが多いんだけど、それを伝えるすべがメッセージの生成・送信しかないので。。

あとメッセージを組み立てるたびに定形のようにエラー処理がついてまわるので、全体では膨大な数になってしまうのもしんどいところ。

その他、よくハマった点

開発途中でハマった点は、メッセージに送信可能な文字数の上限。特にクイックリプライはついあれこれ書いてしまいたくなるのだけど20文字が上限なので、「コードは間違ってないはずだけど、うまくいかないなー」というときはここらへんを見返してみてもいいかもしれません。

以上です

今回、この程度のものではあるけど、通り一遍のものを作ってみて思ったことは、「(僕は)だいたいこれでいいかもなー」ということです。これと似たようなものをPWAで作ろうとコツコツ勉強していたりしたんですが(PWAならPlayストアでも配布できる?し)、正直これで十分じゃんという気持ちになってしまいました。LINEという巨人の肩に乗れる、ということもある。これは非常に大きい.......。

今回の題材である Pixela が、そもそも Web API のみの提供しかしていなかったWebサービスで、もともとチャットボットとの相性が良かった、ということもあると思うんですけどね。

Webフロントエンドやデザインなどについて考えるとどうしても気が重くなってしまう・でもWebサービスの個人開発が好き、というモノグサな自分にとって、こういうUIで使ってもらうこと・この提供方式は全然アリかも!ということがわかったことも、今回大きな収穫でした。