欲しいアプリは自分で作る!

Power Platform や Azure などを利用して作成した業務アプリや趣味アプリなどをご紹介します。

(3. Power Automate 編) 買い物メモを Alexa から LINE に送信する仕組みを Power Automate で作る

前回の続きです。

前回:
(2. LINE 編) 買い物メモを Alexa から LINE に送信する仕組みを Power Automate で作る - 欲しいアプリは自分で作る!


前回の記事までで、 Alexa スキルおよび LINE の設定が終わりました。
本記事では、いよいよ処理の心臓部である Power Automate の作成に入ります。

なお、Power Automate では有償となるプレミアムコネクタを利用します。予めご了承ください。


Power Automate フロー作成手順

Alexa に話しかけた人が、Alexa にどのようなリクエストをしたのかを Power Automate で順番に紐解いていくようなイメージです。

処理内容を確認する

まずはどのような処理が必要かを図化してみます。


f:id:jn-kodama:20200913180019p:plain


…ちょっとごちゃごちゃしていますね。。 1つずつ解説していきます。


まず必要になるのは、「スキル起動のリクエストかどうか」の条件分岐です。

f:id:jn-kodama:20200913180915p:plain

Alexa に処理をリクエストする時の話しかけ方は大きく分けて2種類ありまして、今回の例で言うと

 「Alexa、ラインメモで牛乳を登録して」

のようにスキル名とインテント用発話を一発で全部言ってしまう方法の他に、

 人「Alexa、ラインメモ」
 Alexa「何のメモを追加しますか?」
 人「牛乳を登録して」

のように、ひとまずスキルだけを起動させて、その後にインテント用発話のみを話す方法があります。

「スキル起動のリクエストかどうか」の判断は、上記の「Alexa、ラインメモ」とだけ言ったかどうかを判断するということになります。
「スキル起動のリクエスト」だった場合は、その後に何のメモを追加するかを話してもらう必要があるため、ひとまず Alexa には「何のメモを追加しますか?」と言ってもらい、Alexa と人との会話は継続、つまり Alexa には引き続き音声認識モードを維持してもらうようにします。


続いて必要になるのは、「何らかのインテントのリクエストかどうか」の条件分岐です。

f:id:jn-kodama:20200913181024p:plain

これについては「スキル起動でもない、何のインテントでもない他のリクエスト」がそもそもあるのかどうか分からないため、いいえの場合は何も処理させず終了させることにします。
(それでも分岐を入れているのは、Alexa から渡される情報の中にインテントリクエストかどうかの情報が含まれているため)


続いて必要になるのは、「該当のインテントのリクエストかどうか」の条件分岐です。

f:id:jn-kodama:20200913181038p:plain

スキルには複数のインテントを持たせることができるため、ここで所望のインテントであるかどうかを判断します。
所望のインテントでない場合は、今回はとりあえずスキル起動のリクエストと同様に「何のメモを追加しますか?」と言ってもらうようにします。


最後に、いよいよ今回のインテントに対する処理に入ります。

f:id:jn-kodama:20200913181100p:plain

メモは、前回の記事で設定した3つのスロットに格納されているため、各々のスロットに値が入っているかどうかを条件分岐させてチェックし、入っている場合は都度 LINE の公式アカウントから送信してもらうようにします。
各スロットのチェックが終わったら、Alexa に「メモを追加しました」と言ってもらい、会話を終了させます。
なお、どのスロットにも値が入っていないケースは今回は考慮していません。


フロー作成

いよいよフローの作成に入ります。

まずは全体像から。先ほどの条件分岐のマークも入れてみました。
(文字小さくてすみません)

一見複雑に見えますが、先ほどの図と全く同じ構造になっていますので、先ほどの処理内容の解説がお分かりいただければスッとご理解いただけるかもしれません。

f:id:jn-kodama:20200913205833p:plain f:id:jn-kodama:20200913205839p:plain f:id:jn-kodama:20200913205844p:plain f:id:jn-kodama:20200913205852p:plain f:id:jn-kodama:20200913205857p:plain f:id:jn-kodama:20200913205903p:plain

それでは、順に見ていきます。


トリガー

トリガーは「HTTP 要求の受信時」を利用します。

フローの作成画面で自動府フローを選択、フロー名を入力する画面で「スキップ」を選択し、 f:id:jn-kodama:20200913214636p:plain

トリガーの検索窓に「要求」と入力すると現れます。 f:id:jn-kodama:20200913214644p:plain

Alexa からのデータ(Request Body)を解析するために、JSON スキーマを入力する必要がありますが、ここで前々回の記事(https://jn.hateblo.jp/entry/2020/09/11/234026)の作成手順 19.でコピーした JSON を「サンプルの JSON ペイロード」に貼り付けます。

f:id:jn-kodama:20200913214651p:plain f:id:jn-kodama:20200913214704p:plain

JSON スキーマができました。 f:id:jn-kodama:20200913214711p:plain


なお、フローを保存すると「HTTP POST の URL」が表示されますので、その URL を Alexa スキル側に設定する作業があります(後ほどご案内)。


条件分岐1:スキル起動のリクエスト?

まずは、スキル起動のリクエストかどうかを判断します。
トリガーの下に、「条件」アクションを追加します。
f:id:jn-kodama:20200913222610p:plain

スキル起動のリクエストかどうかは、Alexa から受け取った JSON データの "type" キーの値が "LaunchRequest" になっているかどうかで判断できます。
左側の "値の選択" 欄に、動的なコンテンツで type と検索して表示されたものの上から4番目を選択し、右側の "値の選択" 欄に「LaunchRequest」と入力します。 f:id:jn-kodama:20200913222850p:plain f:id:jn-kodama:20200913222856p:plain

「どの type を選択したか分からない。。」という方は、マウスオーバーして「triggerBody()?['request']?['type']」となっていれば OK です。 f:id:jn-kodama:20200913222520p:plain


続いて、"はい" の場合(スキル起動リクエストの場合)に、「追加するメモを教えてください」と Alexa に言ってもらうための処理を追加します。
"はいの場合" の中に「応答」アクションを追加します。 f:id:jn-kodama:20200913223422p:plain

ヘッダーと本文をそれぞれ以下のように追記します。

[ヘッダー]
 キー:Content-Type
 値:application/json;charset=utf-8

[本文]

{
  "response": {
    "outputSpeech": {
      "text": "追加するメモを教えてください。",
      "type": "PlainText"
    },
    "reprompt": {
      "outputSpeech": {
        "text": "追加するメモを教えてください。",
        "type": "PlainText"
      }
    },
    "shouldEndSession": false
  },
  "version": "1.0"
}

f:id:jn-kodama:20200913223914p:plain

ポイントは本文の JSON 内の "shouldEndSession" です。
これを true にすると会話は終了となり、Alexa はそれ以上会話を聞き取ろうとしなくなります。
今回は引き続きメモの内容を聞き取る必要があるため、false にしておきます。

"はいの場合" の処理はこれで以上なので、応答アクションの下に「終了」アクションを付けて終了させます。
状態は「成功」にしておきます。 f:id:jn-kodama:20200913224305p:plain f:id:jn-kodama:20200913224313p:plain


条件分岐2:インテントのリクエスト?

続いて、インテントのリクエストかどうかを判断します。

インテントのリクエストかどうかは、先ほどと同じ "type" キーの値が "IntentRequest" になっているかどうかで判断できます。
フローの最下部(はいの場合、いいえの場合の枠の下)に「条件」アクションを追加し、左側の "値の選択" 欄に先ほどと同じ type を、右側の "値の選択" 欄に「IntentRequest」を入力します。 f:id:jn-kodama:20200913225128p:plain


続いて、"いいえ" の場合(インテントリクエストではない場合)に、Alexa に何も言わせずに処理を終了するための応答を追加します。
"いいえの場合" の中に「応答」アクションを追加し、ヘッダーと本文をそれぞれ以下のように追記します。

[ヘッダー]
 キー:Content-Type
 値:application/json;charset=utf-8

[本文]

{
  "response": {
    "outputSpeech": {
      "text": "",
      "type": "PlainText"
    },
    "reprompt": {
      "outputSpeech": {
        "text": "",
        "type": "PlainText"
      }
    },
    "shouldEndSession": true
  },
  "version": "1.0"
}

f:id:jn-kodama:20200913225659p:plain

テキストを空にすることで、Alexa に何も言わせないという設定です。
会話を終了させたいので、"shouldEndSession" は true にします。

"いいえの場合" の処理はこれで以上なので、応答アクションの下に「終了」アクションを付けて終了させます。
状態は「成功」にしておきます。

f:id:jn-kodama:20200913225740p:plain


条件分岐3:インテント名は "LINEMemo"?

続いて、リクエストされたインテントが今回扱いたい "LIMEMemo" であるかどうかを判断します。

リクエストされたインテントの内容は、Alexa から受け取った JSON データの "name" キーの値で判断できます。
フローの最下部に「条件」アクションを追加し、左側の "値の選択" 欄に、動的なコンテンツで name と検索して表示されたものの一番上を選択、右側の "値の選択" 欄に「LINEMemo」と入力します。 f:id:jn-kodama:20200913230339p:plain

「どの name を選択したか分からない。。」という方は、マウスオーバーして「triggerBody()?['request']?['intent']?['name']」となっていれば OK です。 f:id:jn-kodama:20200913230621p:plain


続いて、"いいえ" の場合("LINEMemo" インテントではない場合)に、「追加するメモを教えてください」と Alexa に言ってもらうための処理を追加します。
"いいえの場合" の中に「応答」アクションを追加し、ヘッダーと本文をそれぞれ以下のように追記します。

[ヘッダー]
 キー:Content-Type
 値:application/json;charset=utf-8

[本文]

{
  "response": {
    "outputSpeech": {
      "text": "追加するメモを教えてください。",
      "type": "PlainText"
    },
    "reprompt": {
      "outputSpeech": {
        "text": "追加するメモを教えてください。",
        "type": "PlainText"
      }
    },
    "shouldEndSession": false
  },
  "version": "1.0"
}

f:id:jn-kodama:20200913231003p:plain

"いいえの場合" の処理はこれで以上なので、応答アクションの下に「終了」アクションを付けて終了させます。
状態は「成功」にしておきます。 f:id:jn-kodama:20200913231020p:plain


条件分岐4~6:各スロットに値入ってる?

ここから、ようやくメモを登録する処理に入ります。

続いて、スロットにメモの値が入っているかどうかを1つずつ判断します。
メモの値は、Alexa から受け取った JSON データの "value" キーの値が "null" 以外になっているかどうかで判断できます。

フローの最下部に「条件」アクションを3つ追加し、左側の "値の選択" 欄に、動的なコンテンツで value と検索して表示されたものからそれぞれ以下のものを選択します。

  • triggerBody()?['request']?['intent']?['slots']?['slotMemoFirst']?['value']
  • triggerBody()?['request']?['intent']?['slots']?['slotMemoSecond']?['value']
  • triggerBody()?['request']?['intent']?['slots']?['slotMemoThird']?['value']

真ん中の条件を "次の値に等しくない" に変更し、右側の "値の選択" 欄には「null」と入力します。
(この null は一度フローを保存終了して再度開くと消えているのですが、最初に null と書いておかないと正しく条件判定されません)

f:id:jn-kodama:20200914111740p:plain


value が 6個もあって、そもそもどれがどれだか分かんねぇよ!」

はい、そうですよね。汗
実はこれ、トリガー部分で設定した JSON スキーマに登場する順番で並んでいます。スロットに該当する部分だけピックアップしてみましょう。

"slots": {
    "type": "object",
    "properties": {
        "slotMemoThird": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                },
                "value": { ← 1つ目の value(3番目のメモの値)
                    "type": "string"
                },
                "confirmationStatus": {
                    "type": "string"
                },
                "source": {
                    "type": "string"
                },
                "slotValue": {
                    "type": "object",
                    "properties": {
                        "type": {
                            "type": "string"
                        },
                        "value": { ← 2つ目の value
                            "type": "string"
                        }
                    }
                }
            }
        },
        "slotMemoSecond": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                },
                "value": { ← 3つ目の value(2番目のメモの値)
                    "type": "string"
                },
                "confirmationStatus": {
                    "type": "string"
                },
                "source": {
                    "type": "string"
                },
                "slotValue": {
                    "type": "object",
                    "properties": {
                        "type": {
                            "type": "string"
                        },
                        "value": { ← 4つ目の value
                            "type": "string"
                        }
                    }
                }
            }
        },
        "slotMemoFirst": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                },
                "value": { ← 5つ目の value(1番目のメモの値)
                    "type": "string"
                },
                "confirmationStatus": {
                    "type": "string"
                },
                "source": {
                    "type": "string"
                },
                "slotValue": {
                    "type": "object",
                    "properties": {
                        "type": {
                            "type": "string"
                        },
                        "value": { ← 6つ目の value
                            "type": "string"
                        }
                    }
                }
            }
        }
    }
}

ご覧いただくとお分かりになるかと思いますが、そもそもメモの順番が Third → Second → First となっており、今回取りたい value

 First:5番目  Second:3番目  Third:1番目

となります。
(もちろん、トリガー部の JSON スキーマのメモの順番をご自身で分かりやすい順番に書き換えても OK です。)

「どの value を選択したか分からない。。」という方は、マウスオーバーして確認してみてください。


続いて、各々 "はい" の場合(メモの値が入っている場合)に、LINE 公式アカウントからメモを送信するための処理を追加します。
追加した3つの条件の "はいの場合" の中にそれぞれ「HTTP」アクションを追加します。 f:id:jn-kodama:20200914111836p:plain

必要な情報をそれぞれ以下のように追記します。

[方法]
 POST

[URI]
 https://api.line.me/v2/bot/message/push

[ヘッダー]
 キー:Content-Type
 値:application/json
 キー:Authorization
 値:Bearer xxxxxxx(← xxxxxxxは前回の記事の LINE 公式アカウントの登録手順 7.で取得したアクセストークン)

[本文]

{
  "to": "送信したいユーザーのユーザー ID",
  "messages": [
    {
      "text": "[動的なコンテンツから、条件で指定したものと同じ value を選択する]",
      "type": "text"
    }
  ]
}

f:id:jn-kodama:20200914122738p:plain

今回は、特定の1人のユーザーにメッセージを送信する Push メッセージを利用します。
特定のユーザーにメッセージを送信するには宛先情報(上記の to の部分)が必要となりますので、その取得方法について紹介します。


以下のサイトにアクセスし、前回の記事で作成した LINE の公式アカウントを友だち追加したユーザーのプロフィール情報を取得します。
https://developers.line.biz/ja/reference/messaging-api/#get-profile

HTTP リクエストの「試す」をクリックし、以下の情報をペーストして「送信」をクリックします。

 userId path: 前回の記事(https://jn.hateblo.jp/entry/2020/09/13/190634)の手順11.でコピーした公式アカウントの ID
 Authorization: 前回の記事(https://jn.hateblo.jp/entry/2020/09/13/190634)の手順7.でコピーした公式アカウントのアクセストーク

f:id:jn-kodama:20200914122140p:plain

すると、公式アカウントを友だち登録したユーザーの ID が取得できますので、これをコピーして先ほどのフローの to に貼り付けます。 f:id:jn-kodama:20200914122249p:plain

なお、夫婦でメモを共有するなどの用途で2人以上に送信したい場合は、Push 以外の送信方法がありますので、以下をご参照ください。
developers.line.biz


HTTP 設定の後は、"はいの場合" も "いいえの場合" も次に進みますので、ここには "終了" は入れません。

3つ分設定すると、こんな感じになります。 f:id:jn-kodama:20200914112337p:plain


あとは、最後に「メモを追加しました」と Alexa に言ってもらい、終了です。
フローの最下部に「応答」アクションを追加し、ヘッダーと本文をそれぞれ以下のように追記します。

[ヘッダー]
 キー:Content-Type
 値:application/json;charset=utf-8

[本文]

{
  "response": {
    "outputSpeech": {
      "text": "メモを追加しました。",
      "type": "PlainText"
    },
    "reprompt": {
      "outputSpeech": {
        "text": "メモを追加しました。",
        "type": "PlainText"
      }
    },
    "shouldEndSession": true
  },
  "version": "1.0"
}

f:id:jn-kodama:20200914110542p:plain

これ以上の会話は不要なので、"shouldEndSession" を true にすることをお忘れなく。


これで、フローは完成です。
フローに名前を付けて保存します。


Webhook URL(HTTP POST の URL)を Alexa スキルに設定する

最後に、フローを保存したことでフローのトリガー部分に HTTP POST の URL が発行されますので、これをコピーして Alexa スキルに設定します。 f:id:jn-kodama:20200914123615p:plain

前々回の記事で作成した Alexa スキルの編集画面を開き、左側の「エンドポイント」をクリックします。 f:id:jn-kodama:20200914123735p:plain

"サービスのエンドポイントの種類" で HTTPS を選択、"デフォルトの地域" に先ほどコピーした URL を貼り付け、下の "SSL証明書の種類を選択" で上から2番目の「開発用のエンドポイントは、証明機関が発行したワイルドカード証明書をもつドメインサブドメインです」を選択します。

選択したら、「エンドポイントを保存」をクリックして終了です。
f:id:jn-kodama:20200914124206p:plain


以上ですべての設定は終了です。正常にメモが送信されることを確認してください。


まとめ

「料理とか皿洗いとかしている時でも簡単に LINE にメモできるようにしたい」
という妻のひょんな一言から、日常をちょっと便利にするツールができました。

Alexa などのスマートスピーカーは、我が家も買う前は「そんな使わないでしょ」と疑心暗鬼だったのですが、いざ活用してみると非常に便利でもう手放せません!

アプリ作成ももちろん面白いし便利なのですが、スマホを触らずとも操作ができるスマートスピーカーはこれまた実用的で、きっと皆さんも活用したらハッピーになれるはず!という思いで記事にしてみました。
記事のボリュームはそこそこありますが、慣れてしまえばサクサク作れるようになりますので、是非お試しくださいませ。



欲しいツールは欲しい人が作る時代へ。
何かヒントになれば幸いです。


参考文献

今回も、ひらりんさんの以下の記事を大いに参考にさせていただきました。
(おかげさまで、自分でカスタマイズできるようになりました。ありがとうございました!) qiita.com

また、LINE の制御を理解する上で、Hiro さんの以下の記事に助けられました。ありがとうございました! mofumofupower.hatenablog.com

他、LINE は公式リファレンスが充実していますので、LINE 利用にご興味ある方は一度眺めてみることをおススメします! developers.line.biz