1. システムとオフィスの融合
  2. media
  3. Teams Azure
  4. Teams データを取得してみた(2)

QESブログ

Teams データを取得してみた(2)

  • LINEで送る
  • このエントリーをはてなブックマークに追加
本シリーズの構成は、以下になります。
第1回 Teams データを取得してみた(1) Teams API利用のための基本設定やプログラム(C#)からのチャネルメッセージの取得等を試しています。
第2回 Teams データを取得してみた(2) チャネルメッセージ、リプライメッセージやリアクション情報の取得や解析を試しています。
第3回 Teams データを取得してみた(3) Teamsに登録されたファイルの一覧の取得を試しています。

番外編 Power Platform で Teams データを取得してみた Power Platformでデータ取得を試しています。

前回は、投稿データを取得してみました。
今回は、もう少しデータの中身を突っ込んで見ていきたいと思います。
まずは投稿データの解析から。

投稿データを解析する

このような投稿があるチャネルから、投稿データを取得します。
TeamsAPI2-001.png
投稿メッセージを実際に取得したデータは、こちら。
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('a31fdde2-fec2-4cd3-b720-f9ce6f28746f')/channels('19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2')/messages/$entity",
    "id": "1658569118282",
    "replyToId": null,
    "etag": "1661672010717",
    "messageType": "message",
    "createdDateTime": "2022-07-23T09:38:38.282Z",
    "lastModifiedDateTime": "2022-08-28T07:33:30.717Z",
    "lastEditedDateTime": "2022-08-13T05:26:52.931Z",
    "deletedDateTime": null,
    "subject": null,
    "summary": null,
    "chatId": null,
    "importance": "normal",
    "locale": "en-us",
    "webUrl": "https://teams.microsoft.com/l/message/19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2/1658569118282?groupId=a31fdde2-fec2-4cd3-b720-f9ce6f28746f&tenantId=ae9e2ac9-ae71-44cc-9a9e-4082965d23b6&createdTime=1658569118282&parentMessageId=1658569118282",
    "policyViolation": null,
    "eventDetail": null,
    "from": {
        "application": null,
        "device": null,
        "user": {
            "id": "0d00906a-f129-429a-9ab8-4b82b3da253d",
            "displayName": "テスト 太郎",
            "userIdentityType": "aadUser"
        }
    },
    "body": {
        "contentType": "html",
        "content": "<div><div>はじめての投稿です。</div><div>よろしくお願いします。</div></div>"
    },
    "channelIdentity": {
        "teamId": "a31fdde2-fec2-4cd3-b720-f9ce6f28746f",
        "channelId": "19:9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41@thread.tacv2"
    },
    "attachments": [],
    "mentions": [],
    "reactions": [
        {
            "reactionType": "laugh",
            "createdDateTime": "2022-07-23T09:40:34.049Z",
            "user": {
                "application": null,
                "device": null,
                "user": {
                    "id": "b544ebc4-05a4-4a2e-8edf-9d4f3f6d27db",
                    "displayName": null,
                    "userIdentityType": "aadUser"
                }
            }
        },
        {
            "reactionType": "laugh",
            "createdDateTime": "2022-07-23T09:43:53.81Z",
            "user": {
                "application": null,
                "device": null,
                "user": {
                    "id": "c4e6c41f-ee6c-4003-8bd4-1ea4a8cb18a8",
                    "displayName": null,
                    "userIdentityType": "aadUser"
                }
            }
        }
    ]
}
取得したメッセージの内容を見ると、body の content に本文情報が含まれていることが分かります。応答データを見ればある程度の内容は理解できますが、メッセージの仕様は、こちらに記載されています。(⇒メッセージ仕様

主な項目は、以下となっています。
・投稿者:from
・投稿日時:createdDateTime
・タイトル:subject
・本文:body
・リアクション:reactions

本文データは、body に入っていることが分かりましたが、contentType が text と html があります。書式を設定すると、htmlになりそうというのは想像つきますが、書式をつけなくても改行を含むと勝手にhtmlになってしまうようです。そのため、単純なテキストデータとして利用する場合は、htmlタグを除去するなどの対応が必要そうです。(単一行のテキスト投稿の場合は、textになってました。)

他のメッセージも見てみると、投稿した記憶の無いメッセージが含まれています。bodyに、良く分からないタグが入っているものもあります。
 {
  "id": "1660369265795",
  "replyToId": null,
  "etag": "1660369265795",
  "messageType": "unknownFutureValue",
  "createdDateTime": "2022-08-13T05:41:05.795Z",
  "lastModifiedDateTime": "2022-08-13T05:41:05.795Z",
  "lastEditedDateTime": null,
  "deletedDateTime": null,
  "subject": null,
  "summary": null,
  "chatId": null,
  "importance": "normal",
  "locale": "en-us",
  "webUrl": "https://teams.microsoft.com/l/message/19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2/1660369265795?groupId=a31fdde2-fec2-4cd3-b720-f9ce6f28746f&tenantId=ae9e2ac9-ae71-44cc-9a9e-4082965d23b6&createdTime=1660369265795&parentMessageId=1660369265795",
  "from": null,
  "policyViolation": null,
  "body": {
    "contentType": "html",
    "content": "<systemEventMessage/>"
  },
  "channelIdentity": {
    "teamId": "a31fdde2-fec2-4cd3-b720-f9ce6f28746f",
    "channelId": "19:9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41@thread.tacv2"
  },
  "attachments": [],
  "mentions": [],
  "reactions": [],
  "eventDetail": {
    "@odata.type": "#microsoft.graph.membersAddedEventMessageDetail",
    "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z",
    "members": [
      {
        "id": "f4064b50-abe3-442f-a690-9b6abc3bcdae",
        "displayName": null,
        "userIdentityType": "aadUser"
      }
    ],
    "initiator": {
      "application": null,
      "device": null,
      "user": {
        "id": "0d00906a-f129-429a-9ab8-4b82b3da253d",
        "displayName": null,
        "userIdentityType": "aadUser"
      }
    }
  }
},
見た感じ、システムイベントのメッセージのようです。
応答メッセージ全体を、追いかけてみると、
"eventDetail"
に詳細が記載されてそうです。

"@odata.type": "#microsoft.graph.membersAddedEventMessageDetail"
メンバーを追加したイベントのようでした。
どのユーザーが追加されたイベントかは、中身を追っていくと、membersにidが入っていますので特定可能です。
イベントメッセージは、今回の処理では使わないことにしますので、無視することにします。

このようなイベントの話も先ほどのメッセージ仕様のページに以下のような記載がありました。詳しくは公式ドキュメントをご参照ください。
> 存在する場合は、新しいメンバーの追加など、 チャット、 チャネル、または チーム で発生したイベントの詳細を表します。

今回は、ユーザーに投稿されたメッセージだけを取得しようと思っているので、messageType="message" で判定することにします。
取得してみると分かりますが、messages API ではスレッドトップのメッセージしか取得されておらず、そのメッセージに対する返信は取得できません。

返信をどう取得するかというと、メッセージに対して、さらにreplies で要求する必要があります。
(⇒replies の仕様)
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('a31fdde2-fec2-4cd3-b720-f9ce6f28746f')/channels('19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2')/messages('1658569118282')/replies",
    "@odata.count": 2,
    "value": [
        {
            "id": "1658570098941",
            "replyToId": "1658569118282",
            "etag": "1658570171753",
            "messageType": "message",
            "createdDateTime": "2022-07-23T09:54:58.941Z",
            "lastModifiedDateTime": "2022-07-23T09:56:11.753Z",
            "lastEditedDateTime": null,
            "deletedDateTime": null,
            "subject": null,
            "summary": null,
            "chatId": null,
            "importance": "normal",
            "locale": "en-us",
            "webUrl": "https://teams.microsoft.com/l/message/19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2/1658570098941?groupId=a31fdde2-fec2-4cd3-b720-f9ce6f28746f&tenantId=ae9e2ac9-ae71-44cc-9a9e-4082965d23b6&createdTime=1658570098941&parentMessageId=1658569118282",
            "policyViolation": null,
            "eventDetail": null,
            "from": {
                "application": null,
                "device": null,
                "user": {
                    "id": "c6e0f66f-b216-4381-a032-17f0e81c7d43",
                    "displayName": "テスト 五郎",
                    "userIdentityType": "aadUser"
                }
            },
            "body": {
                "contentType": "text",
                "content": "参加しましたよー"
            },
            "channelIdentity": {
                "teamId": "a31fdde2-fec2-4cd3-b720-f9ce6f28746f",
                "channelId": "19:9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41@thread.tacv2"
            },
            "attachments": [],
            "mentions": [],
            "reactions": [
                {
                    "reactionType": "like",
                    "createdDateTime": "2022-07-23T09:56:11.828Z",
                    "user": {
                        "application": null,
                        "device": null,
                        "user": {
                            "id": "0d00906a-f129-429a-9ab8-4b82b3da253d",
                            "displayName": null,
                            "userIdentityType": "aadUser"
                        }
                    }
                }
            ]
        },
        {
            "id": "1658569262582",
            "replyToId": "1658569118282",
            "etag": "1658570168978",
            "messageType": "message",
            "createdDateTime": "2022-07-23T09:41:02.582Z",
            "lastModifiedDateTime": "2022-07-23T09:56:08.978Z",
            "lastEditedDateTime": null,
            "deletedDateTime": null,
            "subject": null,
            "summary": null,
            "chatId": null,
            "importance": "normal",
            "locale": "en-us",
            "webUrl": "https://teams.microsoft.com/l/message/19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2/1658569262582?groupId=a31fdde2-fec2-4cd3-b720-f9ce6f28746f&tenantId=ae9e2ac9-ae71-44cc-9a9e-4082965d23b6&createdTime=1658569262582&parentMessageId=1658569118282",
            "policyViolation": null,
            "eventDetail": null,
            "from": {
                "application": null,
                "device": null,
                "user": {
                    "id": "b544ebc4-05a4-4a2e-8edf-9d4f3f6d27db",
                    "displayName": "テスト 次郎",
                    "userIdentityType": "aadUser"
                }
            },
            "body": {
                "contentType": "text",
                "content": "こちらこそ、よろしくです。"
            },
            "channelIdentity": {
                "teamId": "a31fdde2-fec2-4cd3-b720-f9ce6f28746f",
                "channelId": "19:9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41@thread.tacv2"
            },
            "attachments": [],
            "mentions": [],
            "reactions": [
                {
                    "reactionType": "heart",
                    "createdDateTime": "2022-07-23T09:44:01.9Z",
                    "user": {
                        "application": null,
                        "device": null,
                        "user": {
                            "id": "c4e6c41f-ee6c-4003-8bd4-1ea4a8cb18a8",
                            "displayName": null,
                            "userIdentityType": "aadUser"
                        }
                    }
                },
                {
                    "reactionType": "heart",
                    "createdDateTime": "2022-07-23T09:56:09.054Z",
                    "user": {
                        "application": null,
                        "device": null,
                        "user": {
                            "id": "0d00906a-f129-429a-9ab8-4b82b3da253d",
                            "displayName": null,
                            "userIdentityType": "aadUser"
                        }
                    }
                }
            ]
        }
    ]
}
これで、返信の内容まで取得できました。
返信メッセージのフォーマットは、スレッドトップのメッセージと同様です。

リアクションデータを解析する

メッセージだけでなく、リアクションの状態を使って何らかの判断に使っているという方も多いと思います。例えば、以下のようなものがあります。

・読みましたの既読の印
・何らかのルールでリアクションを決めたアンケートの回答
・単純にメッセージに対する感情的な?反応

何らかのルールで集計、解析することで、データ活用ができるかもしれません。
早速最初に取得した投稿から、リアクションの部分を確認してみると、以下のようになっていました。
2つのリアクションがついていることが分かります。(※画面イメージでも、笑いのリアクションが2つついていますので。)
    "reactions": [
        {
            "reactionType": "laugh",
            "createdDateTime": "2022-07-23T09:40:34.049Z",
            "user": {
                "application": null,
                "device": null,
                "user": {
                    "id": "b544ebc4-05a4-4a2e-8edf-9d4f3f6d27db",
                    "displayName": null,
                    "userIdentityType": "aadUser"
                }
            }
        },
        {
            "reactionType": "laugh",
            "createdDateTime": "2022-07-23T09:43:53.81Z",
            "user": {
                "application": null,
                "device": null,
                "user": {
                    "id": "c4e6c41f-ee6c-4003-8bd4-1ea4a8cb18a8",
                    "displayName": null,
                    "userIdentityType": "aadUser"
                }
            }
        }
    ]
ちなみに、Teamsのリアクションは6つの種類がありますが、
以下に示すコードが [reactionType] に入っています。
like いいね 
laugh 笑い 
sad 悲しい 
surprised びっくり 
angry 怒り 
heart ステキ 

 


(追記)
2022/12 頃から、リアクションの種類が増えたようです。


「怒り」と「悲しい」は、直接選べるところからは消えていました。
ちなみに「怒り」は、スマイルの中の「怒った顔」を選ぶと引き続き「angry」と設定されていました。

追加されたリアクションが、どのように返ってくるのか試してみましたが、
その状態を示す英単語等では無く、絵文字のコードが返ってくるようです。

Graph エクスプローラーで取得すると、json応答の中に、絵文字そのものが表示されています。


リアクションの種類を全て認識して何らかの処理をするのは現実的では無いかもしれませんが、何かの傾向分析等には使えるかもしれません。



リアクションの時間や、ユーザーIDが含まれているので、誰がいつ、そのリアクションをしたかは特定できます。

では、このデータから、誰がリアクションをして、誰がリアクションをしていないのか?を判別してみたいと思います。
例えば、重要なお知らせとかを投稿するチャネルがあって、見たらリアクションしてね!というルールがあるにも関わらず無視してしまっている人を抽出して、管理者が認識できるようにする要件の対応になります。

まずは、そのチャネルにアクセスできる人で無いと、リアクションはできませんので、アクセス可能なユーザーを確認する必要があります。

チャネルの members APIで、そのチャネルにアクセスできるユーザーを取得できます。
 (⇒チャネルメンバーの取得
※チャネルでは無く、チームのメンバーを取得する場合は、こちらになります。
 (⇒チームメンバーの取得)

チャネルメンバーを取得した結果がこちら。
{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#teams('a31fdde2-fec2-4cd3-b720-f9ce6f28746f')/channels('19%3A9Y-zafBu3W_jmU0YwighDGyOtqiSqq-7hBXr7QXFnL41%40thread.tacv2')/members",
    "@odata.count": 4,
    "value": [
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "id": "MCMjMiMjYWU5ZTJhYzktYWU3MS00NGNjLTlhOWUtNDA4Mjk2NWQyM2I2IyMxOTo5WS16YWZCdTNXX2ptVTBZd2lnaERHeU90cWlTcXEtN2hCWHI3UVhGbkw0MUB0aHJlYWQudGFjdjIjI2M0ZTZjNDFmLWVlNmMtNDAwMy04YmQ0LTFlYTRhOGNiMThhOA==",
            "roles": [],
            "displayName": "テスト 三郎",
            "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z",
            "userId": "c4e6c41f-ee6c-4003-8bd4-1ea4a8cb18a8",
            "email": "test.saburo@t2dev2.onmicrosoft.com",
            "tenantId": "ae9e2ac9-ae71-44cc-9a9e-4082965d23b6"
        },
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "id": "MCMjMiMjYWU5ZTJhYzktYWU3MS00NGNjLTlhOWUtNDA4Mjk2NWQyM2I2IyMxOTo5WS16YWZCdTNXX2ptVTBZd2lnaERHeU90cWlTcXEtN2hCWHI3UVhGbkw0MUB0aHJlYWQudGFjdjIjIzBkMDA5MDZhLWYxMjktNDI5YS05YWI4LTRiODJiM2RhMjUzZA==",
            "roles": [
                "owner"
            ],
            "displayName": "テスト 太郎",
            "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z",
            "userId": "0d00906a-f129-429a-9ab8-4b82b3da253d",
            "email": "test.taro@t2dev2.onmicrosoft.com",
            "tenantId": "ae9e2ac9-ae71-44cc-9a9e-4082965d23b6"
        },
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "id": "MCMjMiMjYWU5ZTJhYzktYWU3MS00NGNjLTlhOWUtNDA4Mjk2NWQyM2I2IyMxOTo5WS16YWZCdTNXX2ptVTBZd2lnaERHeU90cWlTcXEtN2hCWHI3UVhGbkw0MUB0aHJlYWQudGFjdjIjI2M2ZTBmNjZmLWIyMTYtNDM4MS1hMDMyLTE3ZjBlODFjN2Q0Mw==",
            "roles": [],
            "displayName": "テスト 五郎",
            "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z",
            "userId": "c6e0f66f-b216-4381-a032-17f0e81c7d43",
            "email": "test.goro@t2dev2.onmicrosoft.com",
            "tenantId": "ae9e2ac9-ae71-44cc-9a9e-4082965d23b6"
        },
        {
            "@odata.type": "#microsoft.graph.aadUserConversationMember",
            "id": "MCMjMiMjYWU5ZTJhYzktYWU3MS00NGNjLTlhOWUtNDA4Mjk2NWQyM2I2IyMxOTo5WS16YWZCdTNXX2ptVTBZd2lnaERHeU90cWlTcXEtN2hCWHI3UVhGbkw0MUB0aHJlYWQudGFjdjIjI2I1NDRlYmM0LTA1YTQtNGEyZS04ZWRmLTlkNGYzZjZkMjdkYg==",
            "roles": [],
            "displayName": "テスト 次郎",
            "visibleHistoryStartDateTime": "0001-01-01T00:00:00Z",
            "userId": "b544ebc4-05a4-4a2e-8edf-9d4f3f6d27db",
            "email": "test.jiro@t2dev2.onmicrosoft.com",
            "tenantId": "ae9e2ac9-ae71-44cc-9a9e-4082965d23b6"
        }
    ]
}
テスト太郎が所有者(owner)で、テスト次郎、テスト三郎、テスト五郎がメンバーであることが分かります。
投稿メッセージにある reactions の user / id とメンバー一覧の userId を比較することで、リアクションしていないユーザーを特定できます。
最初に取得したメッセージのリアクションは、User の displayNameがNULLになっていて分かりにくいですが、User の Idを確認すると テスト次郎とテスト三郎がリアクションしています。ということは、テスト五郎がリアクションしていないことになります。

目視で確認しようとするとしんどいですが、プログラムで確認してしまえば一瞬でできそうです。
プログラムで実装してみた結果がこちら。
TeamsAPI2-002.png

リマインドメールを送ってみる

リアクションしていないのは、単純に気づいていないだけかもしれません。
重要なお知らせだったら、絶対に見て欲しいものもありますので、リマインドメール(催促メール?リアクションしない人へのいやがらせ??)を送ってみます。

Teams API を使うと情報の参照だけでなく登録もできます。
普通のメッセージ投稿もできますし、個人宛のチャットも送ることができます。

リアクションしていない人を抽出できたら、そのユーザーにリマインドチャットを送る実装をしてみます。前回同様、特定のユーザーの権限でAPIをコールしていますので、そのユーザーからのチャットを送信します。
機械的なメッセージを送付するのであれば、ボットから送信しても良いかと思います。

ということで、先ほど抽出した、テスト五郎に催促チャットを送ってみます。
チャットの作成の詳細は(⇒チャットメッセージの送信

チャットを送信する部分のコードは、こんな感じにしました。
User me = await graphClient.Me.Request().GetAsync();

string fromUser = me.Id;
string toUser = リアクションしてくれなかったユーザーのID;

var chat = new Chat
{
    ChatType = ChatType.OneOnOne,
    Members = new ChatMembersCollectionPage()
    {
        new AadUserConversationMember
         {
             Roles = new List()
             {
                 "owner"
             },
             AdditionalData = new Dictionary<string, object>()
             {
                 {"user@odata.bind", $"https://graph.microsoft.com/v1.0/users('{fromUser}')"}
             }
         },
         new AadUserConversationMember
         {
             Roles = new List()
             {
                 "owner"
             },
             AdditionalData = new Dictionary<string, object>()
             {
                 {"user@odata.bind", $"https://graph.microsoft.com/v1.0/users('{toUser}')"}
             }
         }
     }
};

var chatInfo = await graphClient.Chats.Request().AddAsync(chat);

var chatMessage = new ChatMessage
{
    Body = new ItemBody
    {
        ContentType = BodyType.Html,
        Content = @"メッセージ見たら、何らかのリアクションしてね。<a href='メッセージの webUrl'>これ</a>"
    }
};

await graphClient.Chats[chatInfo.Id].Messages.Request().AddAsync(chatMessage);
どのメッセージか分かるようにメッセージへのリンクをつけておきます。メッセージへのリンクは、取得したメッセージ中の [webUrl] にありますので、そちらをつけています。

実行してみると、チャットを送信できました。
リンクもちゃんとついてます。
TeamsAPI2-003.png




リンクをクリックすると、該当メッセージを表示できました。
TeamsAPI2-004.png

次回の記事は、こちら⇒Teams データを取得してみた(3)

最後に

今回は、Teamsの投稿メッセージを解析し、別のアクションにつなげてみました。
データを活用する第一歩といったところでしょうか。
このくらいであれば、コードを書いても割と単純に実装できます。
さらに、Power Apps等のローコードで実装できるところは、ローコードに任せてしまって複雑なところで個別実装するという形も良いでしょう。

QESでは様々なアプリケーションの開発・導入を行っております。
私共が提供するサービス・ソリューションにつきましてはこちらに掲載しております。

システム開発・構築でお困りの問題や弊社が提供するサービス・ソリューションにご興味を抱かれましたら、是非一度お問い合わせください。

その他の技術ブログについてはこちら
QESではクラウドエンジニアを募集しております。

※このブログで参照されている、Microsoft、Windows、Azure、PowerAppsその他のマイクロソフト製品およびサービスは、米国およびその他の国におけるマイクロソフトの商標または登録商標です。
  • LINEで送る
  • このエントリーをはてなブックマークに追加

お気軽にお問い合わせください。

ページのトップへ