Cisco Sparkに別れを告げて、Cisco Webex Teamsを始めてみよう。
Cisco Sparkは、Cisco Webex Teamsになりました。
Thrzn41.CiscoSpark
も、Thrzn41.WebexTeams
になりました。
リポジトリは、 WebexTeamsAPIClientへ移行しています。
https://github.com/thrzn41/WebexTeamsAPIClient
branchやforkではなく、完全に移行することにしました。
Cisco Spark API Client
は、Cisco Spark REST API
を利用しやすくしたライブラリです。
基本的な機能のほかに、Cisco SparkのAPIを使いやすくするための機能を実装しています。
- .NET Standard 1.3以降
- .NET Core 1.0以降
- .NET Framework 4.5.2以降
注記: 簡易Webhookサーバ機能を利用する場合は、
.NET Stardard 2.0+, .NET Core 2.0+, .NET Framework 4.5.2+が必要です。
- Cisco Sparkの基本的なAPI(List/Get/Create Message, Spaceなど)。
- Cisco SparkのAdmin API(List/Get Event, Licenseなど)。
- ストレージに保存するTokenの暗号化と復号。
- List API用のPagination機能。
- Retry-after値の処理とRetry executor。
- Markdown builder
- エラーコードや詳細の取得。
- Webhook secretの検証とWebhook notification manager、Webhook event handler。
- OAuth2 helper
- 簡易Webhookサーバ機能(.NET Standard 2.0+, .NET Core 2.0+, .NET Framework 4.5.2+)。
Sparkのリソース名 | 利用可能な機能 | 説明 |
---|---|---|
Person/People | List/Get | 利用可能。Get Meも利用可能 |
Space(Room) | List/Create/Get/Update/Delete | 利用可能。Roomは、API Clientでは、Spaceと呼ばれる。 |
SpaceMembership(Membership) | List/Create/Get/Update/Delete | 利用可能。Membershipは、API Clientでは、SpaceMembershipと呼ばれる。 |
Message | List/Create/Get/Delete | 利用可能。ローカルのstreamからファイル添付も可能 |
Team | List/Create/Get/Update/Delete | 利用可能。 |
TeamMembership | List/Create/Get/Update/Delete | 利用可能。 |
Webhook | List/Create/Get/Update/Delete | 利用可能。 |
File | GetInfo/GetData/Upload | 利用可能。 |
Sparkのリソース名 | 利用可能な機能 | 説明 |
---|---|---|
Person/People | Create/Update/Delete | 利用可能。 |
Event | List/Get | 利用可能。 |
Organization | List/Get | 利用可能。 |
License | List/Get | 利用可能。 |
Role | List/Get | 利用可能。 |
ProtectedString
が、Token保存時の暗号化と、読み込み時の復号の機能を提供します。
詳細は後述。
Cisco Spark APIのpaginationに関しては、ここを参照。
result.HasNext
とresult.ListNextAsync()
が、Cisco Spark API Clientで利用可能です。
詳細は後述。
result.HasRetryAfter
と result.RetryAfter
が、Cisco Spark API Clientで利用可能です。
また、 RetryExecutor
が利用可能です。
詳細は後述。
result.HttpStatusCode
が、Cisco Spark API Clientで利用可能です。
詳細は後述。
Cisco Spark APIは、エラーコードと詳細を返す場合があります。
result.Data.HasErrors
とresult.Data.GetErrors()
が、Cisco Spark API Clientで利用可能です。
Cisco Spark APIは、部分的なエラーを返す場合があります。
部分エラーの詳細に関しては、ここを参照。
Item.HasErrors
とItem.GetPartialErrors()
が、Cisco Spark API Clientで利用可能です。
trackingIdは、Cisco Spark APIのテクニカルサポートで利用される可能性があります。
result.TrackingId
が、Cisco Spark API Clientで利用可能です。
詳細は後述。
Webhook.CreateEventValidator()
が、Cisco Spark API Clientで利用可能です。
詳細は後述。
Cisco Spark API ClientのCreateWebhookAsync()
メソッドはデフォルトでは、webhook secretを動的に生成します。
Webhook listener機能は、簡易的なWebhookのサーバ機能を提供します。
注記: この機能は、簡単なテスト時の利用を想定しています。
運用環境等では、より信頼性のあるサーバをご利用ください。
WebhookListener
が、Cisco Spark API Clientで利用可能です。
詳細は後述。
Cisco Spark API Client
は、以下のいずれかの方法で、NuGet
package manager経由で入手できます。
-
NuGet Package ManagerのGUI
"Thrzn41.CiscoSpark
"を検索してインストール。 -
NuGet Package ManagerのCLI
PM> Install-Package Thrzn41.CiscoSpark
- .NET Client
> dotnet add package Thrzn41.CiscoSpark
usingディレクティブを利用する場合は、以下の名前空間を指定します。
using Thrzn41.Util
using Thrzn41.CiscoSpark
using Thrzn41.CiscoSpark.Version1
必要に応じて、Thrzn41.CiscoSpark.Version1.Admin
も利用可能です。
Cisco Spark API Clientのインスタンスは可能な限り長い期間使いまわすようにします。
/// 基本API利用時。
SparkAPIClient spark = SparkAPI.CreateVersion1Client(token);
Admin APIを利用する場合は、Admin API用のインスタンスを作成する必要があります。
SparkAdminAPIClient
は、SparkAPIClient
の全機能に加えて、Adminの機能が利用できます。
/// Admin API利用時。
SparkAdminAPIClient spark = SparkAPI.CreateVersion1AdminClient(token);
注記: 'token'は、Cisco Spark APIでは、非常にセンシティブな情報です。
'token'は、慎重に保護する必要があります。
ソースコード中に直接記載したり、安全ではない方法で保存しないようにします。
Cisco Spark API Client
では、トークンを暗号化したり復号する方法を、いくつか提供しています。
独自の方法で暗号化や復号、保護を実装する場合は、インスタンス作成時に、復号されたトークン文字列を利用することができます。
char[] tokens = GetBotTokenFromBotOwner();
var protectedToken = LocalProtectedString.FromChars(tokens);
LocalProtectedString.ClearChars(tokens);
Save("token.dat", protectedToken.EncryptedData);
Save("entropy.dat", protectedToken.Entropy);
注記: LocalProtectedStringはメモリ内での保護は提供していません。
Tokenを保存する際の、暗号化と復号での利用を想定しています。
byte[] encryptedData = Load("token.dat");
byte[] entropy = Load("entropy.dat");
var protectedToken = LocalProtectedString.FromEncryptedData(encryptedData, entropy);
/// 基本API利用時。
SparkAPIClient spark = SparkAPI.CreateVersion1Client(protectedToken);
注記: オプションに応じて、暗号化されたデータは、暗号化したときと同じローカルユーザまたはローカルマシン上でのみ復号できます。
var result = await spark.CreateMessageAsync("xyz_space_id", "こんにちは, Spark!");
if(result.IsSuccessStatus)
{
Console.WriteLine("メッセージが投稿されました: id = {0}", result.Data.Id);
}
result.IsSuccessStatus
を使って成功したかどうか確認できます。
result.Data.HasErrors
やresult.Data.GetErrorMessage()
を使ってCisco Spark APIサービスからエラーコード、エラーメッセージを受け取ることができます。
var result = await spark.CreateMessageAsync("xyz_space_id", "こんにちは, Spark!");
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()
は、リクエスト失敗時にSparkResultException
をスローします。
(一方、result.Data
は、SparkResultException
を送出しません。)
try
{
var result = await spark.CreateMessageAsync("xyz_space_id", "こんにちは, Spark!");
var message = result.GetData();
Console.WriteLine("メッセージが投稿されました: id = {0}", message.Id);
}
catch(SparkResultException sre)
{
Console.WriteLine("メッセージの投稿に失敗しました: status = {0}, trackingId = {1}, description = {2}",
sre.HttpStatusCode, sre.TrackingId, sre.Message);
}
using (var fs = new FileStream("path/myfile.png", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var data = new SparkFileData(fs, "imagefile.png", SparkMediaType.ImagePNG))
{
var result = await spark.CreateMessageAsync("xyz_space_id", "添付ファイル付き", data);
if(result.IsSuccessStatus)
{
Console.WriteLine("添付ファイル付きでメッセージが投稿されました: id = {0}", result.Data.Id);
}
}
var result = await spark.CreateDirectMessageAsync("[email protected]", "こんにちは, Spark!");
if(result.IsSuccessStatus)
{
Console.WriteLine("メッセージが投稿されました: id = {0}", result.Data.Id);
}
var result = await spark.ListSpacesAsync();
if(result.IsSuccessStatus && result.Data.HasItems)
{
foreach (var item in result.Data.Items) {
Console.WriteLine("Space: title = {0}", item.Title);
}
}
ダウンロードせずにファイルの情報だけ入手する。
var result = await spark.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);
}
ファイルをダウンロードする。
var result = await spark.GetFileDataAsync(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);
using(var stream = file.Stream)
{
// streamにファイルのデータが含まれる。
}
}
var result = await spark.ListSpacesAsync();
if(result.IsSuccessStatus)
{
//
// ここで何か処理する...
//
if(result.HasNext)
{
// 続きのリストがあれば取得する。
result = await result.ListNextAsync();
if(result.IsSuccessStatus)
{
// ...
}
}
}
var result = await spark.ListSpacesAsync();
Console.WriteLine("Status is {0}", result.HttpStatusCode);
var result = await spark.ListSpacesAsync();
if(result.IsSuccessStatus)
{
//
// ここで何かする...
//
}
else if(result.HasRetryAfter)
{
Console.WriteLine("{0}後にリトライしなきゃ!!", result.RetryAfter.Delta);
}
RetryExecutor
を利用してリトライ処理を容易にします。
// RetryExecutor.Oneは最大で1回のリトライを試みます。
var result = RetryExecutor.One.ListAsync(
() =>
{
// このメソッドは必要に応じて、リトライされます。
return spark.ListSpacesAsync();
},
(r, retryCount) =>
{
// ここは、リトライが実行される前に呼び出されます。
// ここでリトライ時のログの出力等の処理が可能です。
Log.Info("Retry is required: delta = {0}, counter = {1}", r.RetryAfter.Delta, retryCount);
// 'true'を返すとリトライが実行されます。
return true;
}
);
var result = await spark.ListSpacesAsync();
Console.WriteLine("Tracking id: {0}", result.TrackingId);
var md = new MarkdownBuilder();
// メンションと番号付きリストでMarkdownを作成。
md.Append("こんにちは、").AppendMentionToPerson("xyz_person_id", "〇〇さん").AppendLine();
md.AppendOrderedList("Item1");
md.AppendOrderedList("Item2");
md.AppendOrderedList("Item3");
var result = await spark.CreateMessageAsync("xyz_space_id", md.ToString());
var webhook = await spark.GetWebhookAsync("xyz_webhook_id");
var validator = webhook.CreateEventValidator();
イベントがWebhookのURIに通知された際には、 x-Spark-Signatureがハッシュ値を持っています。
validatorを利用して、データの整合性を確認できます。
byte[] webhookEventData = GetWebhookEventData();
if( validator.Validate(webhookEventData, "xyz_x_spark_signature_value") )
{
Console.WriteLine("通知されたイベントデータの検証に成功!");
}
Webhook notification managerを使ってWebhookへの通知を管理します。
- インスタンスを作成する。
var notificationManager = new WebhookNotificationManager();
- 通知用のfunctionを登録します。
var webhook = await spark.GetWebhookAsync("xyz_webhook_id");
notificationManager.AddNotification(
webhook,
(eventData) =>
{
Console.WriteLine("イベントを受信, id = {0}", eventData.Id);
}
);
- イベントの受信時。
byte[] webhookEventData = GetWebhookEventData();
// Signatureが確認され登録したfunctionにイベントデータが通知されます。
notificationManager.ValidateAndNotify(webhookEventData, "xyz_x_spark_signature_value", encodingOfData);
- 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 spark.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.Resouce == EventResouce.Message)
{
Console.WriteLine("Message, id = {0}", eventData.MessageData.Id);
}
}
);
- Listenerの開始。
Listenerを開始すると、イベント発生時に登録したfunctionに通知されます。
listener.Start();
グローバル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 spark.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.Resouce == EventResouce.Message)
{
Console.WriteLine("Message, id = {0}", eventData.MessageData.Id);
}
}
);
- Listenerの開始。
Listenerを開始すると、イベント発生時に登録したfunctionに通知されます。
listener.Start();