Node-REDでLINE Messaging APIを使ってLINEに音声メッセージを投稿してみた

# はじめに
歳をとってくると親に連絡するのもおっくうになり、声を聞かせたいと思って電話すると延々とよく分からない長話を聞かされたりします。
そこで、一方的ではありますが、毎日LINEで音声メッセージを親に送ろうと考えました。

Node-REDでは「[node-red-contrib-line-messaging-api](https://flows.nodered.org/node/node-red-contrib-line-messaging-api)」という外部ノードが利用できるようなのですが、送れるメッセージ形式に音声メッセージがありませんでした。(※2021/10/1時点)
ですが、LINEからnode.js用のSDKも提供されていますし、APIもしっかりしているので、ノードにするまでもなくお手軽にサブフローで実装してみました。

# 前提環境
- Node-RED v2.0.6
- [LINE Developers](https://developers.line.biz/ja/)で「Messaging API」が利用できる状態

自宅の環境はkubernetesを導入しており、外部公開用にngrokを利用しているので、最終的には以下のようなフローにしています。
（図は理解しやすいように簡略してます）
![スクリーンショット 2021-10-04 10.20.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/91c254ff-2aa0-c92b-c5e6-fbc776bbe812.png)

kubernetesのサービスをngrokで外部に公開する方法は、以下の記事をご参考ください。

https://qiita.com/Y-Shikase/items/7668b991d4cdf5635ee5

もちろんこんな複雑な構成ではなく、クラウドサービスのインスタンスでも「ffmpegの実行」と「音声ファイル置き場」が解決できれば実現可能なケースはあると思います。

# 作成物
- 「LINE署名検証」サブフロー

https://raw.githubusercontent.com/Shikase443/node-red-sample/main/nodered-line-signature-check.json

- 「LINE API」サブフロー
 
https://raw.githubusercontent.com/Shikase443/node-red-sample/main/nodered-line-api-subflow.json

# 「LINE署名検証」サブフローの説明
http-inノードで受信したLINE Messaging APIのwebhookイベントオブジェクトデータの、x-line-signatureに含まれる署名を検証します。
プライベートな特定用途なので、複数のイベントオブジェクトデータには対応しておりません。
複数のイベントオブジェクトを処理させたい場合は、自身で作成したフローにループ処理などを加えてサブフローを配列処理に対応させてください。

![スクリーンショット 2021-10-01 12.58.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/15f74a3f-43cc-bd40-29d8-2144079daff9.png)

サブフロー内部ですが、http-inで受信したpayloadを一旦平文に変換します。

![スクリーンショット 2021-10-01 13.13.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/8ee47ebb-ffbe-14aa-c5c1-187ba5de809c.png)

その後、functionノードでLINE Developerのリファレンスのサンプルのように検証します。

https://developers.line.biz/ja/reference/messaging-api/#signature-validation

![スクリーンショット 2021-10-01 13.01.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/f29c0766-4aa0-923c-cc47-6c87552696e3.png)

functionノード内では、外部モジュールを利用しているので、「設定」タブを以下のように設定しておきます。
これでデプロイ時に外部モジュールをダウンロードしてくれます。最近まで使い方が分からなかったのですが、すごいですね...これ。

![スクリーンショット 2021-10-01 13.01.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/ee09f639-d821-c382-18eb-b8d4e87d774c.png)

署名検証は、LINEプラットフォームから送られたものと判断するための処理ですので、やっておけば悪意のあるWebhookを防げるかもしれません。

署名が正しい場合、受信したpayloadを今度は使い勝手の良いObjectに変換します。
また、webhook受信後は返信する流れが多いと思うので、返信用(replyMessageメソッド)のためにreplyTokenを「msg.replytoken」に保存しています。
再度書いておきますが、単一イベントしか対応させておりません。

![スクリーンショット 2021-10-01 12.54.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/edf44e8c-55f6-c7b9-6485-2c425ae2d121.png)

functionノードで環境変数を取り込んでいますが、サブフローのプロパティで環境変数を入力できるようにしています。
作成したLINEボットの「Channel Secret」を入力するようにUIを作成しています。

![スクリーンショット 2021-10-01 13.27.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/2bbdffdc-9fe8-d2fe-52e4-5161cd0ab0aa.png)

と、署名検証のサブフローはこんな感じです。

# 「LINE API」サブフローの説明
LINE Messaging APIリファレンスと同様のメッセージオブジェクト(JSONオブジェクト)を入力して、PushまたはReplyメソッドを使ってメッセージを送信します。

https://developers.line.biz/ja/reference/messaging-api/#message-objects

全ての形式は確認していませんが、「テキストメッセージ」「音声メッセージ」「位置情報メッセージ」が送れる事を確認しています。
![スクリーンショット 2021-10-01 13.33.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/9473160d-8035-c7a7-33ed-6150f30c1fe3.png)

サブフロー内部ですが、msg.payloadのJSONがObjectになってなかったらObjectに変換して、ReplyかPushの動作をします。
![スクリーンショット 2021-10-01 13.42.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/95dfff6e-738b-c336-d681-c5b96744d8b2.png)

サブフローのプロパティは以下のようになっています。
![スクリーンショット 2021-10-01 14.00.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/a070dbce-ef47-5b33-eef9-4137c0645e9d.png)

Replyは環境変数のTOKEN(ChannelAccessToken)のみを使ってreplyMessageメソッドを読んでいます。
msg.replytokenにreplyTokenが必要ですが、前述の「LINE署名検証」サブフローを使っていれば自動的に入っています。
エラー処理とか入れたい人は入れてください。

![スクリーンショット 2021-10-01 13.44.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/44333931-fa1e-a6f9-a001-5473baf23a88.png)

Pushは環境変数のSECRET(ChannelSecret)とTOKEN(ChannelAccessToken)を使ってpushMessageメソッドを読んでます。
環境変数のUSER(myIdやgroupId)宛にメッセージを送信します。
![スクリーンショット 2021-10-01 13.50.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/2fe4ba2d-218d-a8b1-2a79-864590eb2069.png)

Reply/Pushで利用する、「@line/bot-sdk(lineBotSdk)」外部モジュールの利用は「設定」タブで設定しています。
![スクリーンショット 2021-10-01 13.45.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/854d20e1-84be-a253-14ce-d95f3b9d6605.png)

ほんと、外部モジュールの利用がめちゃくちゃ便利になるので、是非新しいバージョンのNode-REDを活用してください。
最近のNode-REDではノード開発する機会は減っているのかもしれません。

# 実装した内容
「毎日LINEで音声メッセージを親に送ろう」という当初の計画ですが、毎朝7時に今日の日付と天気予報を音声メッセージで送るように実装しました。

![スクリーンショット 2021-10-01 14.15.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/1fc7455c-7145-5808-a17e-35c2f1efa253.png)

TTSで発話メッセージを自動生成しますが、メッセージ内容から説明すると、「日付取得」は「今何時？」サブフローを利用しています。

https://qiita.com/Y-Shikase/items/fc02b4b8543bc2118c2c

「天気予報」については、githubで公開しているサブフローを利用しています。

https://raw.githubusercontent.com/Shikase443/node-red-sample/main/nodered-weather-tsukumijima.json

「発話メッセージ」は、以下のようにtemplateノードのmustacheで組み立てます。
![スクリーンショット 2021-10-01 14.21.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/0444ea2e-02ec-090a-e7a9-f8c2edbe55b2.png)

TTSについては「CoeFont API」サブフローを利用しています。

https://qiita.com/Y-Shikase/items/2d773dc4d970228437d5

「CoeFont CLOUD」からはwav形式で音声が返されるので、ffmpegをexecノードで実行してm4aに変換しています。
また、APIの応答で音声バイナリが返却され、クラウド上に残るわけではないため、Node-RED上でローカルに保存して変換後のm4aファイルは、ファイル名をキーにバイナリを返すようにしています。
ここら辺は環境によっては真似できないかもしれませんので、Google Driveなどを利用しても良いかもしれません。
1点悩んだ点は、LINEの音声メッセージオブジェクトでは音声の長さ(duration)のパラメータが必要になるのですが、ffmpegコマンドの標準エラー出力に出てくるので、execノードの後続で正規表現を利用して取得させています。
もちろん、エラー時もあると思うので、数値かどうか判定させています。

![スクリーンショット 2021-10-01 14.25.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/390b964b-16f0-a6bf-25ff-1507af6b2b01.png)

で、m4aファイルのURLができてしまえば、LINE音声メッセージのオブジェクトをpayloadに設定して、pushMessageすればよいわけです。

![スクリーンショット 2021-10-01 14.48.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410957/e84fab0e-e326-b9fa-3fe2-4eadf7ebadb2.png)

LINEでは毎朝7時に私の声で音声メッセージが投稿されます。（動画が貼れないので、twitterに投稿したものを掲載しておきます。）

https://twitter.com/shikasey/status/1443814528773357572?s=21

Voiced by https://CoeFont.CLOUD

# おわりに
年老いてくると、テキストメッセージが煩わしく感じるので音声メッセージは良いと思います。
そこで格安(500円/月)で高品質の機械学習系TTSが利用できる「CoeFont CLOUD」を組み合わせると、LINEもより温かみのある一風変わったものに見えてきますね。
(ステマじゃないのですが...自分の声のモデルを作成する場合は1回500円〜です)

Node-REDはいろんなものが繋げられて楽しいですね！！
次はwebhookからreplyMesageで音声メッセージを投稿する楽しいものを考えてます。