MoreBeerMorePower

Power Platform中心だけど、ノーコード/ローコード系を書いてます。

Teamsで送信した位置情報から付近のレストランを検索するFlow Botを作る

Teamsのモバイルアプリは位置情報 (現在地だけでなく、地図上の座標) をチャットに投稿できるのですが、これを利用して指定された地点の周辺のレストランを検索してリストを提示してくれるBotを Power Automateで作ってみました。

動作した際のキャプチャは下図の通りです。本当はカルーセル表示したかったのですが、Flow botだとカルーセルのカードを送れないのでAdaptive Cardのリスト系で作りました。

f:id:mofumofu_dance:20201105101153p:plain

※元ネタはLINE DCの下記投稿です。

0. 準備

Hot pepper のAPI利用申請

先にレストラン検索用にHot pepperのAPI利用申請をしておきます。 https://webservice.recruit.co.jp/register/ このURLからメールアドレスを登録するだけでAPIキーが払い出されます。

APIの詳細については以下のリファレンスを参照してください。ここでは、グルメサーチAPIを利用しています。

ホットペッパー | APIリファレンス | リクルートWEBサービス

リクエストに用いるURLは以下のようなものです。送信された位置情報から緯度経度 (lat,lng) を中心に、そこから半径500m (range) に存在するレストランを、おすすめ順 (order) でJSONフォーマットで取得しています。

https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&lat=XXXXXX&lng=YYYYYY&range=2&order=4&count=10&format=json

Teamsの位置情報メッセージ

Teamsで位置情報を送ったときには、Adaptive Cardでメッセージが形成されています。位置情報 (緯度経度) は直接メッセージからはとれないので、送られたAdaptive Cardから抽出 (マップ部分) する必要があります。

f:id:mofumofu_dance:20201105132055p:plain

{
  "type": "AdaptiveCard",
  "body": [
    {
      "selectAction": {
        "url": "https://www.bing.com/maps?rtp=adr.~pos.35.67488136235141_139.7700459285246",
        "title": "Directions",
        "type": "Action.OpenUrl"
      },
      "size": "stretch",
      "url": "https://graph.microsoft.com/beta/teams/9a37377c-8ca36/channels/19:e9cae06ごにょごにょ2ltZ28=/$value",
      "height": "auto",
      "type": "Image"
    },
    {
      "maxLines": 2,
      "size": "large",
      "text": "5-4 Kyobashi 3-Chōme,  Chuo-Ku,  Tokyo",
      "weight": "bolder",
      "wrap": true,
      "type": "TextBlock"
    }
  ],
  "version": "1.0"
}

位置座標はこのカードのうち、"url": "https://www.bing.com/maps?rtp=adr.~pos.35.67488136235141_139.7700459285246",に含まれていることが見て取れます。

Teamsに投稿された位置情報から、Hot pepperのAPIを実行する際には、このURLから緯度経度を分離してパラメータとして渡していきます。

フローの作成

1. トリガー条件

このフローでおそらく一番大事なのはトリガー条件です。今回のフローでは『チャンネルに新しいメッセージが追加されたとき』のトリガーを利用しますが、応答で投稿したFlow botからのAdaptive Cardもトリガーにかかってしまうので、フローが最低でも2回に1回は失敗することになります。

この無駄な失敗を避けるために、以下のようなトリガー条件を設定します。

  1. メッセージの送信元が "Application" ではないこと
  2. メッセージの添付の長さが0でないこと

1 はFlow bot除けの意味です。 2 は通常のメッセージでトリガーしないために追加しました。

二つの式はそれぞれ以下のように表されます。

@equals(triggerBody()?['from']?['application'],null)
@not(equals(length(triggerBody()?['attachments']),0))

f:id:mofumofu_dance:20201105140455p:plain

2. 位置情報の抽出

投稿されたメッセージから位置情報を取得するために、2つの作成アクションを利用します。

f:id:mofumofu_dance:20201105141212p:plain

式をかなり使っていますが、アクション名をそろえていただければコピペで適用できます。

最初の「作成」アクション

@{json(triggerBody()?['attachments']?[0]?['content'])?['body']?[0]?['selectAction']?['url']}

2つ目の「作成」アクション

{
  "lat": @{split(split(outputs('Compose-extract_map_url'),'pos.')?[1],'_')?[0]},
  "lng": @{split(split(outputs('Compose-extract_map_url'),'pos.')?[1],'_')?[1]}
}

3. Hot pepper APIの実行と結果取得

Hot pepper APIはGETで特にヘッダーなどつける必要がないので、ここではOneDrive for Businessの「URLからファイルをアップロード」のアクションを使って結果のjsonを取得します。

f:id:mofumofu_dance:20201105142521p:plain

ソースURLには以下を設定しています。

https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=ここにAPIキー&lat=@{outputs('Compose-define_user_location')?['lat']}&lng=@{outputs('Compose-define_user_location')?['lng']}&range=2&order=4&count=10&format=json

あとは適当なファイル名で保存して、そのコンテンツをOneDrive for Businessの「パスからファイルコンテンツを取得」アクションで取得しています。

この2つのアクションでHTTPリクエストを置き換えられました。

4. APIの結果から必要な要素を抽出

APIの結果には非常に多くの要素がふくまれており、このままだと扱いづらいので、先に「選択」アクションで必要なデータ (列) だけを抜き出しておきます。

f:id:mofumofu_dance:20201105142927p:plain

「選択」アクションの1つ目の入力には

@{json(outputs('Get_file_content_using_path')?['body'])?['results']?['shop']}

マップ部分は以下をそれぞれ設定しています。

{
  "name": @{item()?['name']},
  "img": @{item()?['photo']?['pc']?['m']},
  "address": @{item()?['address']},
  "url": @{item()?['urls']?['pc']},
  "mapurl": "https://www.bing.com/maps?rtp=adr.~pos.@{item()?['lat']}_@{item()?['lng']}",
  "id": @{item()?['id']}
}

このアクションを入れることで、必要な要素だけを含む配列が得られました。

f:id:mofumofu_dance:20201105143213p:plain

5. Adaptive Cardの形式にマッピング

Adaptive Cardのリスト形式カードを作る場合の方法は以前の投稿を参考にしてください。

mofumofupower.hatenablog.com

マッピングには「選択」アクションを追加、先ほどの「選択」の結果を入力として、以下をマップに設定します。

{
  "type": "Container",
  "separator": true,
  "items": [
    {
      "type": "ColumnSet",
      "columns": [
        {
          "type": "Column",
          "width": 90,
          "items": [
            {
              "type": "TextBlock",
              "text": "@{item()?['name']}",
              "wrap": true,
              "size": "Large"
            }
          ]
        },
        {
          "type": "Column",
          "width": 10,
          "items": [
            {
              "type": "Image",
              "url": "https://github.com/mofumofu-dance/PowerApps365/raw/master/misc/down-arrow.png",
              "selectAction": {
                "type": "Action.ToggleVisibility",
                "targetElements": [
                  "inner_@{item()?['id']}",
                  "hide_@{item()?['id']}",
                  "show_@{item()?['id']}"
                ]
              },
              "id": "show_@{item()?['id']}"
            },
            {
              "type": "Image",
              "url": "https://github.com/mofumofu-dance/PowerApps365/raw/master/misc/up-arrow.png",
              "spacing": "None",
              "id": "hide_@{item()?['id']}",
              "selectAction": {
                "type": "Action.ToggleVisibility",
                "targetElements": [
                  "inner_@{item()?['id']}",
                  "hide_@{item()?['id']}",
                  "show_@{item()?['id']}"
                ]
              },
              "isVisible": false
            }
          ]
        }
      ]
    },
    {
      "type": "Container",
      "items": [
        {
          "type": "Image",
          "url": "@{item()?['img']}",
          "spacing": "None",
          "height": "180px",
          "horizontalAlignment": "center"
        },
        {
          "type": "TextBlock",
          "text": "@{item()?['address']}",
          "wrap": true
        },
        {
          "type": "ActionSet",
          "actions": [
            {
              "type": "Action.OpenUrl",
              "title": "Open in Map",
              "url": "@{item()?['mapurl']}"
            },
            {
              "type": "Action.OpenUrl",
              "title": "Open HP",
              "url": "@{item()?['url']}"
            }
          ]
        }
      ],
      "id": "inner_@{item()?['id']}",
      "isVisible": false
    }
  ]
}

今回はアイテムの開閉を入れているので少し複雑ですが、カードの開閉・表示非表示は以下で解説しています。

mofumofupower.hatenablog.com

以上で、カードの本体は完成です。

f:id:mofumofu_dance:20201105143844p:plain

6. 送信

最後にチャンネルにAdaptive Cardを送信して終わりです。

f:id:mofumofu_dance:20201105143929p:plain

改めて、フローの全体は下図のようになります。

f:id:mofumofu_dance:20201105144000p:plain

おわり

動作は以下の投稿を見てみてください。

今回のフローはエクスポートしてGithubに置いてあります。 インポートして接続情報やチーム・チャンネルを修正いただければそのまま使えると思いますので、参考にしてみてください。(Hot pepperのAPI申請は必要)

PowerApps365/restaurantsearch_flow.zip at master · mofumofu-dance/PowerApps365 · GitHub