Teamsのチャネルメッセージを完璧にエクスポートしたい

2020年6月5日

Teamsのチームは用途が終わったら削除する前提で設定されていますが、削除前に会話のエクスポートが出来ません。なのでこんな感じで困ります。

  • 情報システム部「もうそのチーム使い終わったでしょ!成果物のファイルを整理して、チーム削除して」
  • チームオーナー「削除してもいいけど、会話は後で参照したいから残しといて」
  • 情報システム部「ぐぬぬ」

よっていつまで経ってもゴミチームが残ったままです。

Teamsのチャネルメッセージをエクスポートする方法

1. セキュリティ/コンプライアンスからエクスポート

セキュリティ/コンプライアンスを使えば、Teamsのチャネルメッセージをエクスポートできます。しかしながら

  • チャネル毎にエクスポートできない
  • 親投稿や返信の関係性をうまくエクスポートできない

という問題があります。あくまで監査用であり、ユーザーが見やすい形でエクスポートはできないようです。

2. GraphAPIを使ってエクスポート

APIを使えばチャネル毎に親投稿や返信の関係性も含めてエクスポートできます。

ということで、2でやります。

技術的な話

APIはこちら。

こんな感じで取得します。

  • チーム特定(ID取得)
    • GET /groups/
  • チーム/チャネル特定(ID取得)
    • GET /teams/{id}/channels/
  • 指定したチャネルの親投稿一覧取得
    • GET /teams/{id}/channels/{id}/messages/
  • 指定した親投稿への返信取得
    • GET /teams/{id}/channels/{id}/messages/{id}/replies

他の事例はこちら。

てことで、取るだけなら割と簡単です。

完璧なエクスポート

ここからが本題です。Teamsのチャネルメッセージにはさまざまな形式で投稿できるため、完全な再現を目指すとそれらを全て取得する必要があります。考慮すべきメッセージ種別を一覧にしてみました。

  • ユーザーの投稿
    • メッセージ属性
      • 投稿者
      • 投稿日時
      • 編集日時
    • メッセージヘッド
      • 投稿/アナウンス
      • アナウンスヘッド
      • アナウンス背景画像
      • サブヘッド(件名)
    • メッセージ重要度(重要/普通)
    • メッセージ本文欄
      • メンション
        • 個人メンション
        • チャネルメンション
        • チームメンション
      • メッセージ本文
        • 日本語
        • 日本語以外
        • 絵文字
        • Giphy
        • ステッカー
      • HTML装飾
        • 太字/斜体/下線/取り消し線
        • ハイライト/フォントの色/フォントサイズ
        • 段落(見出し1/見出し2/見出し3/段落/等幅
        • インデント/箇条書き/段落番号
        • 引用/リンク/コードスニペット
        • 段落罫線/表
      • 画像挿入
        • 画像
        • gif画像(そもそも挿入可能?)
      • 添付ファイル
      • リアクション(いいね)
        • いいね/ハート/笑い/びっくり/悲しい/怒っている
        • リアクション者
  • ユーザー以外の投稿
    • botの投稿
    • コネクタ
    • IncomingWebhook
    • メール投稿

うーん、めんどくさい。

単純な取得

ひとまず少しやってみましょう。

こいつをエクスポートしたのがこちらです。ちなみに以下のAPIをGETで叩いてます。

https://graph.microsoft.com/beta/teams/738f192b-de2d-4cb2-bda8-0273cc7299ba/channels/19:51b38c312f234daa9bb8e9d05aafd796@thread.skype/messages/1565397760720/replies

{
“@odata.context": “https://graph.microsoft.com/beta/$metadata#teams('738f192b-de2d-4cb2-bda8-0273cc7299ba’)/channels('19%3A51b38c312f234daa9bb8e9d05aafd796%40thread.skype’)/messages('1565397760720’)/replies",
“@odata.count": 5,
“value": [
{
“id": “1565398720134",
“replyToId": “1565397760720",
“etag": “1565436221717",
“messageType": “message",
“createdDateTime": “2019-08-10T00:58:40.134Z",
“lastModifiedDateTime": “2019-08-10T11:23:41.717Z",
“deletedDateTime": null,
“subject": “",
“summary": null,
“importance": “normal",
“locale": “en-us",
“webUrl": “https://teams.microsoft.com/l/message/19%3A51b38c312f234daa9bb8e9d05aafd796%40thread.skype/1565398720134?groupId=738f192b-de2d-4cb2-bda8-0273cc7299ba&tenantId=1ae8725b-e1ff-45f3-b4ff-fb21ab4dc17f&createdTime=1565398720134&parentMessageId=1565397760720",
“policyViolation": null,
“from": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": “Administrator MOD",
“userIdentityType": “aadUser"
}
},
“body": {
“contentType": “html",
“content": “<div><div>\n<div>\n\n<div itemprop=\"copy-paste-block\">\n\n<div style=\"font-size:14px\">\n\n<div style=\"font-size:14px\">10_convでの返信です。いいねなどを付与します<span class=\"animated-emoticon-20-smile\" title=\"スマイル\" type=\"(smile)\"><img itemid=\"smile\" itemscope=\"\" itemtype=\"http://schema.skype.com/Emoji\" src=\"https://statics.teams.microsoft.com/evergreen-assets/skype/v2/smile/20.png\" alt=\"😄\" style=\"width:20px; height:20px\"></span></div>\n</div>\n</div>\n</div>\n</div>\n</div>"
},
“attachments": [],
“mentions": [],
“reactions": [
{
“reactionType": “heart",
“createdDateTime": “2019-08-10T00:58:44.29Z",
“user": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": null,
“userIdentityType": “aadUser"
}
}
}
]
},
{
“id": “1565398694352",
“replyToId": “1565397760720",
“etag": “1565398707790",
“messageType": “message",
“createdDateTime": “2019-08-10T00:58:14.352Z",
“lastModifiedDateTime": “2019-08-10T00:58:27.79Z",
“deletedDateTime": null,
“subject": null,
“summary": null,
“importance": “normal",
“locale": “en-us",
“webUrl": “https://teams.microsoft.com/l/message/19%3A51b38c312f234daa9bb8e9d05aafd796%40thread.skype/1565398694352?groupId=738f192b-de2d-4cb2-bda8-0273cc7299ba&tenantId=1ae8725b-e1ff-45f3-b4ff-fb21ab4dc17f&createdTime=1565398694352&parentMessageId=1565397760720",
“policyViolation": null,
“from": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": “Administrator MOD",
“userIdentityType": “aadUser"
}
},
“body": {
“contentType": “html",
“content": “<div><div>\n<div>\n\n<div itemprop=\"copy-paste-block\">\n\n<div style=\"font-size:14px\">10_convでの返信です。</div>\n\n\n<div style=\"font-size:14px\">改行付き。HTML形式(拡張ボックスを開かない)</div>\n</div>\n</div>\n</div>\n</div>"
},
“attachments": [],
“mentions": [],
“reactions": []
},
{
“id": “1565398692352",
“replyToId": “1565397760720",
“etag": “1565398692352",
“messageType": “message",
“createdDateTime": “2019-08-10T00:58:12.352Z",
“lastModifiedDateTime": null,
“deletedDateTime": null,
“subject": null,
“summary": null,
“importance": “normal",
“locale": “en-us",
“webUrl": “https://teams.microsoft.com/l/message/19%3A51b38c312f234daa9bb8e9d05aafd796%40thread.skype/1565398692352?groupId=738f192b-de2d-4cb2-bda8-0273cc7299ba&tenantId=1ae8725b-e1ff-45f3-b4ff-fb21ab4dc17f&createdTime=1565398692352&parentMessageId=1565397760720",
“policyViolation": null,
“from": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": “Administrator MOD",
“userIdentityType": “aadUser"
}
},
“body": {
“contentType": “html",
“content": “<div>\n<div itemprop=\"copy-paste-block\">\n\n<div style=\"font-size:14px\">10_convでの返信です。</div>\n\n\n<div style=\"font-size:14px\">改行付き。テキスト形式(拡張ボックスを開かない)</div>\n</div>\n</div>"
},
“attachments": [],
“mentions": [],
“reactions": []
},
{
“id": “1565398674509",
“replyToId": “1565397760720",
“etag": “1565398674509",
“messageType": “message",
“createdDateTime": “2019-08-10T00:57:54.509Z",
“lastModifiedDateTime": null,
“deletedDateTime": null,
“subject": null,
“summary": null,
“importance": “normal",
“locale": “en-us",
“webUrl": “https://teams.microsoft.com/l/message/19%3A51b38c312f234daa9bb8e9d05aafd796%40thread.skype/1565398674509?groupId=738f192b-de2d-4cb2-bda8-0273cc7299ba&tenantId=1ae8725b-e1ff-45f3-b4ff-fb21ab4dc17f&createdTime=1565398674509&parentMessageId=1565397760720",
“policyViolation": null,
“from": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": “Administrator MOD",
“userIdentityType": “aadUser"
}
},
“body": {
“contentType": “text",
“content": “10_convでの返信。HTML形式です。ただ、投稿はただのテキストでやります。"
},
“attachments": [],
“mentions": [],
“reactions": []
},
{
“id": “1565397786658",
“replyToId": “1565397760720",
“etag": “1565397786658",
“messageType": “message",
“createdDateTime": “2019-08-10T00:43:06.658Z",
“lastModifiedDateTime": null,
“deletedDateTime": null,
“subject": null,
“summary": null,
“importance": “normal",
“locale": “en-us",
“webUrl": “https://teams.microsoft.com/l/message/19%3A51b38c312f234daa9bb8e9d05aafd796%40thread.skype/1565397786658?groupId=738f192b-de2d-4cb2-bda8-0273cc7299ba&tenantId=1ae8725b-e1ff-45f3-b4ff-fb21ab4dc17f&createdTime=1565397786658&parentMessageId=1565397760720",
“policyViolation": null,
“from": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": “Administrator MOD",
“userIdentityType": “aadUser"
}
},
“body": {
“contentType": “text",
“content": “10_convでの返信です。"
},
“attachments": [],
“mentions": [],
“reactions": []
}
]
}

返信が降順に並んでるなぁとかいろいろ思う所はありますが、本文欄に注目してみましょう。

拡張メッセージボックスを展開せず、一行だけで記載したメッセージはHTML形式ではなくテキスト形式で保管されるようです。

“body": {
“contentType": “text",
“content": “10_convでの返信です。"
},

一方、改行を含めたメッセージを投稿すると、HTML形式になるようです。まぁ一行だけで記載したメッセージ以外はのきなみHTML形式になるのでしょうか。つーか文字の大きさはfontで定義されてるのですね。

“body": {
“contentType": “html",
“content": “<div><div>\n<div>\n\n<div itemprop=\"copy-paste-block\">\n\n<div style=\"font-size:14px\">10_convでの返信です。</div>\n\n\n<div style=\"font-size:14px\">改行付き。HTML形式(拡張ボックスを開かない)</div>\n</div>\n</div>\n</div>\n</div>"
},

絵文字なんかも再現されるようです。Unicodeで定義されてるやつなんでしょうか?

“body": {
“contentType": “html",
“content": “<div><div>\n<div>\n\n<div itemprop=\"copy-paste-block\">\n\n<div style=\"font-size:14px\">\n\n<div style=\"font-size:14px\">10_convでの返信です。いいねなどを付与します<span class=\"animated-emoticon-20-smile\" title=\"スマイル\" type=\"(smile)\"><img itemid=\"smile\" itemscope=\"\" itemtype=\"http://schema.skype.com/Emoji\" src=\"https://statics.teams.microsoft.com/evergreen-assets/skype/v2/smile/20.png\" alt=\"😄\" style=\"width:20px; height:20px\"></span></div>\n</div>\n</div>\n</div>\n</div>\n</div>"
},

いいねも取得できますが、いいねをしたユーザーはIDでしか表現されないので、名前まで表示するなら別途ユーザー情報とマッチングしなければいけないですね。

{
“reactionType": “heart",
“createdDateTime": “2019-08-10T00:58:44.29Z",
“user": {
“application": null,
“device": null,
“conversation": null,
“user": {
“id": “72c2cf01-fc7b-4b30-8020-798e87831dcc",
“displayName": null,
“userIdentityType": “aadUser"
}

…ということで、完璧なエクスポートをしようとすると

  • Teamsのメッセージ種別に対してAPIでどのようにエクスポートされるかを確認
  • それをどのように再現するかを設計

する必要があります。

さて、個人でこれを全てフォローするのはかなり面倒なことが分かったので、後は弊社開発部門のメンバーに丸投げしようと思います(がんばって!) 気分が向いてくれればサクっとツール作って外販できるようになってるかもしれません。あるいはAvePoint社BitTitan社のような3rdパーティー製ソリューションを利用するのもアリですね。

まぁこの手のツールは目的から考えたクオリティにすればいいので、ひとまずメッセージが見れればいい、いいね!なんかは要らない、など要件を定義して、エクスポートする要素を限定するとツールが作りやすいと思いました。

Posted by tera