Skip to content

Latest commit

 

History

History
795 lines (582 loc) · 26.7 KB

README.ja-JP.md

File metadata and controls

795 lines (582 loc) · 26.7 KB

Webex Teams API Client for .NET

nuget MIT license

Webex Teams API Clientは、Cisco Webex Teams REST APIを利用しやすくしたライブラリです。
基本的な機能のほかに、Cisco Webex TeamsのAPIを使いやすくするための機能を実装しています。

ほかの言語のREADME


Webex Teams API Clientを使って、Cisco Webex Teams REST APIを簡単に実行できます。

  • Markdownでメッセージを投稿する簡単な例
// 暗号化されたトークンをストレージから読み込む。
ProtectedString token = LoadEncryptedBotToken();

// TeamsAPIClientのインスタンスを作成する(リトライ機能付き)。
var teams = TeamsAPI.CreateVersion1Client(token, new TeamsRetryHandler(4));

// Markdownを構成します。
var markdown = new MarkdownBuilder();
markdown.Append("こんにちは、").AppendBold("Webex Teams").Append("!!");

// メッセージを投稿します。
var message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com", markdown.ToString())).GetData();

Console.WriteLine("メッセージが投稿されました: ID = {0}", message.Id);
  • AdaptiveCardをJson文字列やobjectから生成して投稿する簡単な例
// Json文字列から生成。
var card = AdaptiveCardAttachment.FromJsonString(
@"
    {
        ""type"": ""AdaptiveCard"",
        ""version"": ""1.0"",
        ""body"": [
        {
            ""type"": ""TextBlock"",
            ""text"": ""Adaptive Cards"",
            ""size"": ""large""
        }
        ],
        ""actions"": [
        {
            ""type"": ""Action.OpenUrl"",
            ""url"": ""http://adaptivecards.io"",
            ""title"": ""Learn More""
        }
        ]
    }
");

// カード付きでメッセージを投稿。
var message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com",
    "[Learn More](http://adaptivecards.io) about Adaptive Card.",
    card)).GetData();

Console.WriteLine("Message was posted: ID = {0}", message.Id);


// 匿名型やクラスのインスタンスからも生成できます。
var cardObj = new
{
    type    = "AdaptiveCard",
    version = "1.0",
    body    = new []
    {
        new
        {
            type = "TextBlock",
            text = "Adaptive Cards",
            size = "large",
        }
    },
    actions = new []
    {
        new
        {
            type  = "Action.OpenUrl",
            url   = "http://adaptivecards.io",
            title = "Learn More"
        }
    },
};

// objectから生成。
card = AdaptiveCardAttachment.FromObject(cardObj);

// カード付きでメッセージを投稿。
message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com",
    "[Learn More](http://adaptivecards.io) about Adaptive Card.",
    card)).GetData();

Console.WriteLine("Message was posted: ID = {0}", message.Id);

// teams.GetAttachmentActionAsync()でユーザからの入力を受け取れます。
// 本APIClientのWebhook listner, event handlerには、これらの入力を受け取る機能があります。
  • ゲストユーザ用のアカウントを発行して投稿する例

Webex TeamsのGuest Issuer機能を利用する例です。

// 暗号化されたGuest Issuer secretをストレージから読み込む。
ProtectedString secret = LoadEncryptedGuestIssuerSecret();

// GuestIssuerClientのインスタンスを作成する。
var guestIssuer = TeamsAPI.CreateVersion1GuestIssuerClient(secret, "your_guest_issuer_id");


// Guest Userを作成する。
var guest = (await guestIssuer.CreateGuestUserAsync("my-guest-id", "ゲストユーザ名")).GetData();

// ゲストユーザ用のTeamsAPIClientインスタンスを作成する(リトライ機能付き)。
var teams = TeamsAPI.CreateVersion1Client(guest, new TeamsRetryHandler(4));

// ゲストユーザからメッセージを投稿する。
var message = (await teams.CreateDirectMessageAsync("your_webex_teams_account@example.com", "こんにちは、私はゲストユーザです!!")).GetData();

Console.WriteLine("メッセージが投稿されました: ID = {0}", message.Id);
  • Pagination機能を利用する例

Webex Temas REST APIのPagination機能の例が以下になります。
スペースを50個ずつ取得していって、特定の名前のスペースへ「こんにちは」を投稿します。

// すべてのGroupスペースを取得するためにEnumeratorを取得します。
var e = (await teams.ListSpacesAsync(
                       type: SpaceType.Group,
                       max: 50)
        ).GetListResultEnumerator();

// すべてのスペースを取得するまで繰り返します。
while (await e.MoveNextAsync())
{
  var r = e.CurrentResult;

  if (r.IsSuccessStatus && r.Data.HasItems)
  {
    // それぞれの結果にスペースのリストが含まれます。
    foreach (var space in r.Data.Items)
    {
      // 特定の名前のスペースに、「こんにちは」と投稿します。
      if (space.Title == "Webex Teams API Client(Thrzn41.WebexTeams)のデモ用スペース")
      {
        await teams.CreateMessageAsync(space, "こんにちは、Webex Teams!!");
      }
    }

  }
}

ほかのサンプルは、 こちら


利用可能なプラットフォーム

  • .NET Standard 1.3以降
  • .NET Core 1.0以降
  • .NET Framework 4.5.2以降

注記: 簡易Webhookサーバ機能を利用する場合は、
.NET Standard 2.0+, .NET Core 2.0+, .NET Framework 4.5.2+が必要です。


サンプル

Webex Teams API Clientのサンプルは、 こちらをクリック


利用可能な機能

  • Webex Teamsの基本的なAPI(List/Get/Create Message, Spaceなど)。
  • Webex TeamsのAdmin API(List/Get Event, Licenseなど)。
  • ストレージに保存するTokenの暗号化と復号。
  • AdaptiveCardsの添付(カードの作成やユーザ入力の取得)。
  • List API用のPagination機能。Paginationを簡単にするためのEnumerator。
  • Retry-after値の処理とRetry handler。
  • Markdown builder。
  • エラーコードや詳細の取得。
  • Webhook secretの検証とWebhook notification manager、Webhook event handler。
  • Guest Issuer helper。
  • OAuth2 helper。
  • 簡易Webhookサーバ機能(.NET Standard 2.0+, .NET Core 2.0+, .NET Framework 4.5.2+)。

基本機能

Teamsのリソース名 利用可能な機能 説明
Person/People List/Get v1.2.2から利用可能。Get Meも利用可能
Space(Room) List/Create/Get/Update/Delete v1.2.2から利用可能。Roomは、API Clientでは、Spaceと呼ばれる。
Space(Room) Meeting Info Get v1.7.1から利用可能。
SpaceMembership(Membership) List/Create/Get/Update/Delete v1.2.2から利用可能。Membershipは、API Clientでは、SpaceMembershipと呼ばれる。
Message List/Create/Get/Delete v1.2.2から利用可能。ローカルのstreamからファイル添付も可能
AdaptiveCards List/Create/Get/Delete v1.7.1から利用可能。
AttachmentActions Create/Get v1.7.1から利用可能。
Team List/Create/Get/Update/Delete v1.2.2から利用可能。
TeamMembership List/Create/Get/Update/Delete v1.2.2から利用可能。
Webhook List/Create/Get/Update/Delete v1.2.2から利用可能。
File GetInfo/GetData/Upload v1.2.2から利用可能。
Place/Device/xAPI - v1.8.1で計画中。

Admin機能

Teamsのリソース名 利用可能な機能 説明
Person/People Create/Update/Delete v1.2.2から利用可能。
Event List/Get v1.2.2から利用可能。
Organization List/Get v1.2.2から利用可能。
License List/Get v1.2.2から利用可能。
Role List/Get v1.2.2から利用可能。
GroupResource List/Get v1.2.2から利用可能。
GroupResourceMembership List/Get/Update v1.2.2から利用可能。

ストレージのTokenの暗号化と復号

ProtectedStringが、Token保存時の暗号化と、読み込み時の復号の機能を提供します。
詳細は後述。

Pagination機能

Cisco Webex Teams APIのpaginationに関しては、ここを参照。

result.HasNextresult.ListNextAsync()が、Webex Teams API Clientで利用可能です。
詳細は後述。

Retry-Afterの取得

result.HasRetryAfterresult.RetryAfterが、Webex Teams API Clientで利用可能です。
また、 TeamsRetryHandlerTeamsRetryOnErrorHandlerが利用可能です。
詳細は後述。

HTTP Statusコードの取得

result.HttpStatusCodeが、Webex Teams API Clientで利用可能です。
詳細は後述。

エラーコードと詳細の取得

Cisco Webex Teams APIは、エラーコードと詳細を返す場合があります。
result.Data.HasErrorsresult.Data.GetErrors()が、Webex Teams API Clientで利用可能です。

部分エラーの取得

Cisco Webex Teams APIは、部分的なエラーを返す場合があります。
部分エラーの詳細に関しては、ここを参照。
Item.HasErrorsItem.GetPartialErrors()が、Webex Teams API Clientで利用可能です。

trackingIdの取得

trackingIdは、Cisco Webex Teams APIのテクニカルサポートで利用される可能性があります。

result.TrackingIdが、Webex Teams API Clientで利用可能です。
詳細は後述。

Validates webhook secret

Webhook.CreateEventValidator()が、Webex Teams API Clientで利用可能です。
詳細は後述。

Webex Teams API ClientのCreateWebhookAsync()メソッドはデフォルトでは、webhook secretを動的に生成します。

Webhook listener(.NET Standard 2.0+, .NET Core 2.0+, .NET Framework 4.5.2+)

Webhook listener機能は、簡易的なWebhookのサーバ機能を提供します。

注記: この機能は、簡単なテスト時の利用を想定しています。
運用環境等では、より信頼性のあるサーバをご利用ください。

WebhookListenerが、Webex Teams API Clientで利用可能です。
詳細は後述。


基本的な使い方

Webex Teams API Clientのインストール

Webex Teams API Clientは、以下のいずれかの方法で、NuGet package manager経由で入手できます。

  • NuGet Package ManagerのGUI
    "Thrzn41.WebexTeams"を検索してインストール。

  • NuGet Package ManagerのCLI

PM> Install-Package Thrzn41.WebexTeams
  • .NET Client
> dotnet add package Thrzn41.WebexTeams

Webex Teams API Client関連のusingディレクティブ

usingディレクティブを利用する場合は、以下の名前空間を指定します。

using Thrzn41.Util
using Thrzn41.WebexTeams
using Thrzn41.WebexTeams.Version1

必要に応じて、以下も利用可能です。

  • Guest Issuer helper利用時、Thrzn41.WebexTeams.Version1.GuestIssuer
  • OAuth2 helper利用時、Thrzn41.WebexTeams.Version1.OAuth2
  • Admin API利用時、Thrzn41.WebexTeams.Version1.Admin

Webex Teams API Clientインスタンスの作成

Webex Teams API Clientのインスタンスは可能な限り長い期間使いまわすようにします。

/// 基本API利用時。
TeamsAPIClient teams = TeamsAPI.CreateVersion1Client(token);

Admin APIを利用する場合は、Admin API用のインスタンスを作成する必要があります。
TeamsAdminAPIClientは、TeamsAPIClientの全機能に加えて、Adminの機能が利用できます。

/// Admin API利用時。
TeamsAdminAPIClient teams = TeamsAPI.CreateVersion1AdminClient(token);

注記: 'token'は、Cisco Webex Teams APIでは、非常にセンシティブな情報です。
'token'は、慎重に保護する必要があります。
ソースコード中に直接記載したり、安全ではない方法で保存しないようにします。
Webex Teams API Clientでは、トークンを暗号化したり復号する方法を、いくつか提供しています。
独自の方法で暗号化や復号、保護を実装する場合は、インスタンス作成時に、復号されたトークン文字列を利用することができます。

暗号化したTokenをストレージに保存する

char[] tokens = GetBotTokenFromBotOwner();

var protectedToken = LocalProtectedString.FromChars(tokens);
LocalProtectedString.ClearChars(tokens);

Save("token.dat",   protectedToken.EncryptedData);
Save("entropy.dat", protectedToken.Entropy);

注記: LocalProtectedStringはメモリ内での保護は提供していません。
Tokenを保存する際の、暗号化と復号での利用を想定しています。

暗号化したTokenをストレージから読み込んで、Webex Teams API Clientのインスタンスを作成する

byte[] encryptedData = Load("token.dat");
byte[] entropy       = Load("entropy.dat");

var protectedToken = LocalProtectedString.FromEncryptedData(encryptedData, entropy);

/// 基本API利用時。
TeamsAPIClient teams = TeamsAPI.CreateVersion1Client(protectedToken);

注記: オプションに応じて、暗号化されたデータは、暗号化したときと同じローカルユーザまたはローカルマシン上でのみ復号できます。

Cisco Webex Teamsのスペースにメッセージを投稿する

var result = await teams.CreateMessageAsync("xyz_space_id", "こんにちは, Teams!");

if(result.IsSuccessStatus)
{
   Console.WriteLine("メッセージが投稿されました: id = {0}", result.Data.Id);
}

成功, 失敗, エラーのハンドリング

result.IsSuccessStatusを使って成功したかどうか確認できます。
result.Data.HasErrorsresult.Data.GetErrorMessage()を使ってCisco Webex Teams APIサービスからエラーコード、エラーメッセージを受け取ることができます。

var result = await teams.CreateMessageAsync("xyz_space_id", "こんにちは, Teams!");

if(result.IsSuccessStatus)
{
   Console.WriteLine("メッセージが投稿されました: id = {0}", result.Data.Id);
}
else
{
  Console.WriteLine("メッセージの投稿に失敗しました: status = {0}, trackingId = {1}", result.HttpStatusCode, result.TrackingId);

  if(result.Data.HasErrors)
  {
    Console.WriteLine( result.Data.GetErrorMessage() );
  }
}

例外を捕捉したい場合は、データ取得時に、result.GetData()を利用することができます。
result.GetData()は、リクエスト失敗時にTeamsResultExceptionをスローします。
(一方、result.Dataは、TeamsResultExceptionを送出しません。)

try
{
  var result = await teams.CreateMessageAsync("xyz_space_id", "こんにちは, Teams!");

  var message = result.GetData();

  Console.WriteLine("メッセージが投稿されました: id = {0}", message.Id);
}
catch(TeamsResultException tre)
{
  Console.WriteLine("メッセージの投稿に失敗しました: status = {0}, trackingId = {1}, description = {2}",
                      tre.HttpStatusCode, tre.TrackingId, tre.Message);
}

Cisco Webex Teamsのスペースに添付ファイル付きでメッセージを投稿する

using (var fs   = new FileStream("path/myfile.png", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var data = new TeamsFileData(fs, "imagefile.png", TeamsMediaType.ImagePNG))
{
    var result = await teams.CreateMessageAsync("xyz_space_id", "添付ファイル付き", data);

    if(result.IsSuccessStatus)
    {
       Console.WriteLine("添付ファイル付きでメッセージが投稿されました: id = {0}", result.Data.Id);
    }
}

Cisco Webex Teamsの1:1スペースにメッセージを投稿する

var result = await teams.CreateDirectMessageAsync("targetuser@example.com", "こんにちは, Teams!");

if(result.IsSuccessStatus)
{
   Console.WriteLine("メッセージが投稿されました: id = {0}", result.Data.Id);
}

スペースの一覧を取得する

var result = await teams.ListSpacesAsync();

if(result.IsSuccessStatus && result.Data.HasItems)
{
  foreach (var item in result.Data.Items) {
    Console.WriteLine("Space: title = {0}", item.Title);
  }  
}

ファイルの情報やデータを取得する

ダウンロードせずにファイルの情報だけ入手する。

var result = await teams.GetFileInfoAsync(new Uri("https://api.example.com/path/to/file.png"));

if(result.IsSuccessStatus)
{
  var file = result.Data;

  Console.WriteLine("File: Name = {0}, Size = {1}, Type = {2}", file.Name, file.Size?.Value, file.MediaType?.Name);
}

ファイルをダウンロードする。
この例では、ファイルのデータがMemoryStreamにコピーされます。

using(var stream = new MemoryStream())
{
  var result = await teams.CopyFileDataToStreamAsync(new Uri("https://api.example.com/path/to/file.png"), stream);

  if(result.IsSuccessStatus)
  {
    var file = result.Data;

    Console.WriteLine("File: Name = {0}, Size = {1}, Type = {2}", file.Name, file.Size?.Value, file.MediaType?.Name);
  }
}

Pagination機能を利用する

var result = await teams.ListSpacesAsync();

if(result.IsSuccessStatus)
{
  //
  // ここで何か処理する...
  //

  if(result.HasNext)
  {
    // 続きのリストがあれば取得する。
    result = await result.ListNextAsync();

    if(result.IsSuccessStatus)
    {
      // ...
    }
  }
}

Enumeratorを使ってPaginaionを利用する

// すべてのGroupスペースを取得するためにEnumeratorを取得します。
var e = (await teams.ListSpacesAsync(
                       type: SpaceType.Group,
                       max: 50)
        ).GetListResultEnumerator();

// すべてのスペースを取得するまで繰り返します。
while (await e.MoveNextAsync())
{
  var r = e.CurrentResult;

  if (r.IsSuccessStatus && r.Data.HasItems)
  {
    // それぞれの結果にスペースのリストが含まれます。
    foreach (var space in r.Data.Items)
    {
      Console.WriteliLine("Title = {0}", space.Title);
    }

  }
}

Http status codeを取得する

var result = await teams.ListSpacesAsync();

Console.WriteLine("Status is {0}", result.HttpStatusCode);

Retry afterを取得する.

var result = await teams.ListSpacesAsync();

if(result.IsSuccessStatus)
{
  //
  // ここで何かする...
  //
}
else if(result.HasRetryAfter)
{
  Console.WriteLine("{0}後にリトライしなきゃ!!", result.RetryAfter.Delta);  
}

Retry Handler

TeamsRetryHandlerTeamsRetryOnErrorHandlerを利用してリトライ処理を容易にします。

// リトライ機能付きでインスタンスを作成(この場合は、最大4回のリトライ)。
var teams = TeamsAPI.CreateVersion1Client(token, new TeamsRetryHandler(4));

// リクエスト時に、HTTP 429のレスポンスがあると、リトライが行われます。
var result = await teams.GetMeAsync();


//HTTP 500, 502, 503, 504レスポンス時にもリトライしたい場合は、TeamsRetryOnErrorHandlerを利用します。
// Retry-Afterヘッダがない場合は、この例の場合は、15秒後にリトライが行われます。
var teams = TeamsAPI.CreateVersion1Client(token, new TeamsRetryOnErrorHandler(4, TimeSpan.FromSeconds(15.0f)));

TrackingIdを取得する

var result = await teams.ListSpacesAsync();

Console.WriteLine("Tracking id: {0}", result.TrackingId);  

Markdown Builder.

var md = new MarkdownBuilder();

// メンションと番号付きリストでMarkdownを作成。
md.Append("こんにちは、").AppendMentionToPerson("xyz_person_id", "〇〇さん").AppendLine();
md.AppendOrderedList("Item1");
md.AppendOrderedList("Item2");
md.AppendOrderedList("Item3");

var result = await teams.CreateMessageAsync("xyz_space_id", md.ToString());

Webhookに通知されたデータを検証する

var webhook = await teams.GetWebhookAsync("xyz_webhook_id");

var validator = webhook.CreateEventValidator();

イベントがWebhookのURIに通知された際には、 X-Spark-Signatureがハッシュ値を持っています。

validatorを利用して、データの整合性を確認できます。

byte[] webhookEventData = GetWebhookEventData();

if( validator.Validate(webhookEventData, "xyz_x_teams_signature_value") )
{
  Console.WriteLine("通知されたイベントデータの検証に成功!");
}

Webhookの通知管理

Webhook notification managerを使ってWebhookへの通知を管理します。

  • インスタンスを作成する。
var notificationManager = new WebhookNotificationManager();
  • 通知用のfunctionを登録します。
var webhook = await teams.GetWebhookAsync("xyz_webhook_id");

notificationManager.AddNotification(
  webhook,
  (eventData) =>
  {
    Console.WriteLine("イベントを受信, id = {0}", eventData.Id);
  }
);
  • イベントの受信時。
byte[] webhookEventData = GetWebhookEventData();

// Signatureが確認され登録したfunctionにイベントデータが通知されます。
notificationManager.ValidateAndNotify(webhookEventData, "xyz_x_teams_signature_value", encodingOfData);

Webhook Listener

  • Webhook listenerのインスタンスの作成。
var listener = new WebhookListener();
  • 待ち受けする、ホストとポートを登録する。

待ち受けには、TLS/httpsを利用すべきです。
そのためには、まず、実行環境側でnetshツールなどを利用して、有効な証明書をバインドしておく必要があります。

バインドされたアドレスとポート番号で、エンドポイントを追加します。

var endpointUri = listener.AddListenerEndpoint("yourwebhookserver.example.com", 8443);
  • Webhook listener用のWebhookを作成します。

listener.AddListenerEndpoint()が返すendpointUriがWebhookの通知先Uriになります。

var result = await teams.CreateWebhookAsync(
  "my webhook for test",
  endpointUri,
  EventResource.Message,
  EventType.Created);
  • Webhook listenerにWebhookと通知先funcを登録します。
var webhook = result.Data;

listener.AddNotification(
  webhook,
  async (eventData) =>
  {
    Console.WriteLine("Eventが通知されました, id = {0}", eventData.Id);

    if(eventData.Resource == EventResource.Message)
    {
      Console.WriteLine("Message, id = {0}", eventData.MessageData.Id);
    }
  }
);
  • Listenerの開始。

Listenerを開始すると、イベント発生時に登録したfunctionに通知されます。

listener.Start();

Webhook listenerをngrokと共に利用する

グローバルIPアドレスが利用できない場合、 ngrokなどのトンネリングサービスが便利な場合があります。

  • ngrokの入手と起動。

ngrokのコマンドラインツールは、ここから入手できます。

以下のコマンドで、トンネリングサービスを起動して、localhostの8080ポートにフォワードされます。

prompt> ngrok http 8080 --bind-tls=true
  • Webhook listenerのインスタンスの作成。
var listener = new WebhookListener();
  • 待ち受けする、ホストとポートを登録する。

ngrokは、localhostへフォワードします。

var endpointUri = listener.AddListenerEndpoint("localhost", 8080, false);
  • Webhook listener用のWebhookを作成します。

この例では、ngronのトンネリングサービスを利用しています。
listener.AddListenerEndpoint()が返したUriは、フォワード先のUriです。

Webhookには、ngrok側のUriを指定する必要があります。

ngrokが、https://ngrok-xyz.example.comを割り当てた場合、
String.Format("https://ngrok-xyz.example.com{0}", endpointUri.AbsolutePath)をWebhookの宛先として指定します。

var result = await teams.CreateWebhookAsync(
  "テスト用のWebhook",
  new Uri(String.Format("https://ngrok-xyz.example.com{0}", endpointUri.AbsolutePath)),
  EventResource.Message,
  EventType.Created);
  • Webhook listenerにWebhookと通知先funcを登録します。
var webhook = result.Data;

listener.AddNotification(
  webhook,
  async (eventData) =>
  {
    Console.WriteLine("Eventが通知されました, id = {0}", eventData.Id);

    if(eventData.Resource == EventResource.Message)
    {
      Console.WriteLine("Message, id = {0}", eventData.MessageData.Id);
    }
  }
);
  • Listenerの開始。

Listenerを開始すると、イベント発生時に登録したfunctionに通知されます。

listener.Start();