Microsoft Graph Client LibraryでTeamsのメッセージを取得する

Microsoft Teamsに投稿されたメッセージをプログラムで取得したい、そんなときはGraph APIを使って取得します。 ナマでREST通信するのはめんどくさいですが、C#で作るのであれば便利なライブラリが用意されています。 この記事ではそのライブラリを使ってメッセージを取得する例をまとめました。

ライブラリについて

Microsoft Graph .Net Client Library。Graph API とのやりとりを簡単にしてくれます。NugetでMicrosoft.Graphという名前で公開されていますので簡単にプロジェクトにインストールできます。Graph APIのBeta機能を使いたいときはMicroosft.Graph.Betaという名の別のパッケージが用意されていますので、そちらをインストールしてください。チームメッセージの取得は2020年5月時点でBeta機能となります。

github.com

メッセージ取得方法

Teamsにはチャネル内のメッセージング機能だけではなく、ユーザーを指定した1 on 1チャットの機能もあります。こちらのチャットのメッセージの取得方法は別なのでご注意ください。
Graph APIの仕様上、自分がアクセス権を持っているメッセージを一度にすべて取得することはできません。メッセージを取得するには、必ずそのメッセージが投稿されたチャネルのIDを指定しなければいけないためです。また、1度に取得できるメッセージの数も決まっていたり、メッセージの返信を取得するには返信元のメッセージのIDが必要などなかなか複雑で、それはライブラリを使っても変わらず、複数回に分けてメッセージを取得する必要があります。
Team(チーム) -> Channel(チャネル) -> Message(メッセージ) -> Message Reply(メッセージの返信) という階層構造をイメージしていただき、それぞれの情報を取得するには親階層のIDが必要となります。

ライブラリの初期化

GraphServiceClientというクラスをインスタンス化します。下の例では匿名関数の中でGraph APIへの認証トークンをセットしています。これは一例であっていろいろなやり方が選択できますので、認証トークンの取得を含め、詳しい説明はMicrosoftのドキュメントを参照してください。

_client = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
    {
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
        await Task.FromResult(0);
    })
);

チームの取得

まず大元となるチームのIDを取得します。チームのIDがわかっている場合は必要ありませんが、チームのIDがわからない場合は、自分の所属するチームの一覧を取得します。

var teams = await _client.Me.JoinedTeams.Request().GetAsync();
foreach (var team in teams)
{
    Console.WriteLine($"Team Id:{team.Id} DisplayName:{team.DisplayName}");
}

チャネルの取得

チャネルのIDを取得します。そのチャネルが所属するチームのIDを指定します。

var channels = await _client.Teams[teamId].Channels.Request().GetAsync();
foreach (var channel in channels)
{
    Console.WriteLine($"Channel Id:{channel.Id} DisplayName:{channel.DisplayName}");
}

メッセージの取得

メッセージを取得します。メッセージが投稿されたチャネル、チームのIDが必要になります。

var messages = await _client.Teams[teamId].Channels[channelId].Messages.Delta().Request().GetAsync(); // 差分がいらない場合はMesssages.Request().GetAsync();
foreach (var message in messages)
{
    Console.WriteLine($"Message Id:{message.Id} Content:{message.Body.Content}");
}

メッセージは最新の投稿順に取得できますが一度に最大50件までしか取得できません。そのため、それ以上のメッセージがチャネル内にある場合は複数回に分けて取得する必要があります。 初回にリクエストした戻り値の中に、NextPageRequestという値があります。その値がNullでない場合は続きがある、ということになります。その値のGetAsyncメソッドを呼ぶことで続きのメッセージを取得できます。

var messages = await messages.NextPageRequest.GetAsync();

メッセージ取得は差分読み込みをサポートしています。 最後まで読み込んだ(NextPageRequestがNullになった)時に、次回取得時にここまで読み切ったという情報をGraphAPIに渡すためのリンクが取得できます。

if (messages.AdditionalData.ContainsKey("@odata.deltaLink"))
{
    deltaLink = messages.AdditionalData["@odata.deltaLink"].ToString();
}

このリンクをどこかに保存しておき、次回メッセージを取得する際にリクエストにセットすることで差分のメッセージのみを取得できます。

var messages = new ChatMessageDeltaCollectionPage();
messages.InitializeNextPageRequest(_client, deltaLink);
messages = await messages.NextPageRequest.GetAsync();

ここらへんのGraph APIの仕様について、詳しくはこちらを参照してください。

docs.microsoft.com

メッセージの返信の取得

1 on 1チャットと違い、チャネル内メッセージには、メッセージとその返信という2つの階層に分かれています。返信を取得するにはメッセージのIDが必要になります。

var messages = await _client.Teams[teamId].Channels[channelId].Messages[messageId].Replies.Request().GetAsync();
foreach (var message in messages)
{
    Console.WriteLine($"Message Id:{message.Id} Content:{message.Body.Content}");
}

返信もメッセージと同じく一度に取得できる数が決まっているため、戻り値のNextPageRequestの値がNullでなかった場合は続きがあり、GetAsyncメソッドを呼ぶことで続きを取得できます。
また、差分取得に関しては、返信自体には用意されていませんが、返信が追加されるとその返信元となるメッセージのほうの差分に乗ってきます。そのため、親となるメッセージを取得後、あらためて返信を取ってくる、という実装になるかと思います。