diff --git a/README.md b/README.md index 7ece4c80..d54fa8aa 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,8 @@ var clientId = "... your client ID ..."; var clientSecret = "... your client secret ..."; var accountId = "... your account id ..."; var connectionInfo = new OAuthConnectionInfo(clientId, clientSecret, accountId, - (_, newAccessToken) => - { + (_, newAccessToken) => + { /* Server-to-Server OAuth does not use a refresh token. That's why I used '_' as the first parameter in this delegate declaration. Furthermore, ZoomNet will take care of getting a new access token @@ -147,7 +147,7 @@ var connectionInfo = new OAuthConnectionInfo(clientId, clientSecret, accountId, In fact, this delegate is completely optional when using Server-to-Server OAuth. Feel free to pass a null value in lieu of a delegate. */ - }); + }); var zoomClient = new ZoomClient(connectionInfo); ``` @@ -166,20 +166,20 @@ using Microsoft.AspNetCore.Mvc; namespace WebApplication1.Controllers { - [Route("api/[controller]")] - [ApiController] - public class ZoomWebhooksController : ControllerBase - { - [HttpPost] - public async Task ReceiveEvent() - { - var parser = new ZoomNet.WebhookParser(); - var event = await parser.ParseEventWebhookAsync(Request.Body).ConfigureAwait(false); - - // ... do something with the event ... - - return Ok(); - } + [Route("api/[controller]")] + [ApiController] + public class ZoomWebhooksController : ControllerBase + { + [HttpPost] + public async Task ReceiveEvent() + { + var parser = new ZoomNet.WebhookParser(); + var event = await parser.ParseEventWebhookAsync(Request.Body).ConfigureAwait(false); + + // ... do something with the event ... + + return Ok(); + } } } ``` @@ -203,31 +203,31 @@ using Microsoft.AspNetCore.Mvc; namespace WebApplication1.Controllers { - [Route("api/[controller]")] - [ApiController] - public class ZoomWebhookController : ControllerBase - { - [HttpPost] - public async Task ReceiveEvent() - { - // Your webhook app's secret token - var secretToken = "... your app's secret token ..."; - - // Get the signature and the timestamp from the request headers - // SIGNATURE_HEADER_NAME and TIMESTAMP_HEADER_NAME are two convenient constants provided by ZoomNet so you don't have to remember the actual names of the headers - var signature = Request.Headers[ZoomNet.WebhookParser.SIGNATURE_HEADER_NAME].SingleOrDefault(); - var timestamp = Request.Headers[ZoomNet.WebhookParser.TIMESTAMP_HEADER_NAME].SingleOrDefault(); - - var parser = new ZoomNet.WebhookParser(); - - // The signature will be automatically validated and a security exception thrown if unable to validate - var zoomEvent = await parser.VerifyAndParseEventWebhookAsync(Request.Body, secretToken, signature, timestamp).ConfigureAwait(false); - - // ... do something with the event... - - return Ok(); - } - } + [Route("api/[controller]")] + [ApiController] + public class ZoomWebhookController : ControllerBase + { + [HttpPost] + public async Task ReceiveEvent() + { + // Your webhook app's secret token + var secretToken = "... your app's secret token ..."; + + // Get the signature and the timestamp from the request headers + // SIGNATURE_HEADER_NAME and TIMESTAMP_HEADER_NAME are two convenient constants provided by ZoomNet so you don't have to remember the actual names of the headers + var signature = Request.Headers[ZoomNet.WebhookParser.SIGNATURE_HEADER_NAME].SingleOrDefault(); + var timestamp = Request.Headers[ZoomNet.WebhookParser.TIMESTAMP_HEADER_NAME].SingleOrDefault(); + + var parser = new ZoomNet.WebhookParser(); + + // The signature will be automatically validated and a security exception thrown if unable to validate + var zoomEvent = await parser.VerifyAndParseEventWebhookAsync(Request.Body, secretToken, signature, timestamp).ConfigureAwait(false); + + // ... do something with the event... + + return Ok(); + } + } } ``` @@ -243,23 +243,23 @@ using Microsoft.AspNetCore.Mvc; namespace WebApplication1.Controllers { - [Route("api/[controller]")] - [ApiController] - public class ZoomWebhooksController : ControllerBase - { - [HttpPost] - public async Task ReceiveEvent() - { - // Your webhook app's secret token - var secretToken = "... your app's secret token ..."; - - var parser = new ZoomNet.WebhookParser(); - var event = await parser.ParseEventWebhookAsync(Request.Body).ConfigureAwait(false); - - var endpointUrlValidationEvent = zoomEvent as EndpointUrlValidationEvent; - var responsePayload = endpointUrlValidationEvent.GenerateUrlValidationResponse(secretToken); - return Ok(responsePayload); - } + [Route("api/[controller]")] + [ApiController] + public class ZoomWebhooksController : ControllerBase + { + [HttpPost] + public async Task ReceiveEvent() + { + // Your webhook app's secret token + var secretToken = "... your app's secret token ..."; + + var parser = new ZoomNet.WebhookParser(); + var event = await parser.ParseEventWebhookAsync(Request.Body).ConfigureAwait(false); + + var endpointUrlValidationEvent = zoomEvent as EndpointUrlValidationEvent; + var responsePayload = endpointUrlValidationEvent.GenerateUrlValidationResponse(secretToken); + return Ok(responsePayload); + } } } ``` @@ -273,54 +273,54 @@ using Microsoft.AspNetCore.Mvc; namespace WebApplication1.Controllers { - [Route("api/[controller]")] - [ApiController] - public class ZoomWebhooksController : ControllerBase - { - [HttpPost] - public async Task ReceiveEvent() - { - // Your webhook app's secret token - var secretToken = "... your app's secret token ..."; - - // SIGNATURE_HEADER_NAME and TIMESTAMP_HEADER_NAME are two convenient constants provided by ZoomNet so you don't have to remember the actual name of the headers - var signature = Request.Headers[ZoomNet.WebhookParser.SIGNATURE_HEADER_NAME].SingleOrDefault(); - var timestamp = Request.Headers[ZoomNet.WebhookParser.TIMESTAMP_HEADER_NAME].SingleOrDefault(); - - var parser = new ZoomNet.WebhookParser(); - Event zoomEvent; - - if (!string.IsNullOrEmpty(signature) && !string.IsNullOrEmpty(timestamp)) - { - try - { - zoomEvent = await parser.VerifyAndParseEventWebhookAsync(Request.Body, secretToken, signature, timestamp).ConfigureAwait(false); - } - catch (SecurityException e) - { - // Unable to validate the data. Therefore you should consider the request as suspicious - throw; - } - } - else - { - zoomEvent = await parser.ParseEventWebhookAsync(Request.Body).ConfigureAwait(false); - } - - if (zoomEvent.EventType == EventType.EndpointUrlValidation) - { - // It's important to include the payload along with your HTTP200 response. This is how you let Zoom know that your URL is valid - var endpointUrlValidationEvent = zoomEvent as EndpointUrlValidationEvent; - var responsePayload = endpointUrlValidationEvent.GenerateUrlValidationResponse(secretToken); - return Ok(responsePayload); - } - else - { - // ... do something with the event ... - - return Ok(); - } - } + [Route("api/[controller]")] + [ApiController] + public class ZoomWebhooksController : ControllerBase + { + [HttpPost] + public async Task ReceiveEvent() + { + // Your webhook app's secret token + var secretToken = "... your app's secret token ..."; + + // SIGNATURE_HEADER_NAME and TIMESTAMP_HEADER_NAME are two convenient constants provided by ZoomNet so you don't have to remember the actual name of the headers + var signature = Request.Headers[ZoomNet.WebhookParser.SIGNATURE_HEADER_NAME].SingleOrDefault(); + var timestamp = Request.Headers[ZoomNet.WebhookParser.TIMESTAMP_HEADER_NAME].SingleOrDefault(); + + var parser = new ZoomNet.WebhookParser(); + Event zoomEvent; + + if (!string.IsNullOrEmpty(signature) && !string.IsNullOrEmpty(timestamp)) + { + try + { + zoomEvent = await parser.VerifyAndParseEventWebhookAsync(Request.Body, secretToken, signature, timestamp).ConfigureAwait(false); + } + catch (SecurityException e) + { + // Unable to validate the data. Therefore you should consider the request as suspicious + throw; + } + } + else + { + zoomEvent = await parser.ParseEventWebhookAsync(Request.Body).ConfigureAwait(false); + } + + if (zoomEvent.EventType == EventType.EndpointUrlValidation) + { + // It's important to include the payload along with your HTTP200 response. This is how you let Zoom know that your URL is valid + var endpointUrlValidationEvent = zoomEvent as EndpointUrlValidationEvent; + var responsePayload = endpointUrlValidationEvent.GenerateUrlValidationResponse(secretToken); + return Ok(responsePayload); + } + else + { + // ... do something with the event ... + + return Ok(); + } + } } } ``` @@ -346,10 +346,10 @@ var subscriptionId = "... your subscription id ..."; // See instructions below h // This is the async delegate that gets invoked when a webhook event is received var eventProcessor = new Func(async (webhookEvent, cancellationToken) => { - if (!cancellationToken.IsCancellationRequested) - { - // Add your custom logic to process this event - } + if (!cancellationToken.IsCancellationRequested) + { + // Add your custom logic to process this event + } }); // Configure cancellation (this allows you to press CTRL+C or CTRL+Break to stop the websocket client) @@ -357,16 +357,16 @@ var cts = new CancellationTokenSource(); var exitEvent = new ManualResetEvent(false); Console.CancelKeyPress += (s, e) => { - e.Cancel = true; - cts.Cancel(); - exitEvent.Set(); + e.Cancel = true; + cts.Cancel(); + exitEvent.Set(); }; // Start the websocket client using (var client = new ZoomWebSocketClient(clientId, clientSecret, accountId, subscriptionId, eventProcessor, proxy, logger)) { - await client.StartAsync(cts.Token).ConfigureAwait(false); - exitEvent.WaitOne(); + await client.StartAsync(cts.Token).ConfigureAwait(false); + exitEvent.WaitOne(); } ``` diff --git a/Source/ZoomNet.IntegrationTests/Program.cs b/Source/ZoomNet.IntegrationTests/Program.cs index 5158cc40..235c3094 100644 --- a/Source/ZoomNet.IntegrationTests/Program.cs +++ b/Source/ZoomNet.IntegrationTests/Program.cs @@ -90,7 +90,8 @@ private static LoggingConfiguration GetNLogConfiguration() // Send logs to console var consoleTarget = new ColoredConsoleTarget(); nLogConfig.AddTarget("ColoredConsole", consoleTarget); - nLogConfig.AddRule(new LoggingRule("*", NLog.LogLevel.Warn, NLog.LogLevel.Fatal, consoleTarget) { RuleName = "ColoredConsoleRule" }); + nLogConfig.AddRule(NLog.LogLevel.Warn, NLog.LogLevel.Fatal, consoleTarget, "*"); + nLogConfig.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, consoleTarget, "ZoomNet.ZoomWebSocketClient"); return nLogConfig; } diff --git a/Source/ZoomNet.IntegrationTests/TestsRunner.cs b/Source/ZoomNet.IntegrationTests/TestsRunner.cs index 5f95d9aa..12af7c50 100644 --- a/Source/ZoomNet.IntegrationTests/TestsRunner.cs +++ b/Source/ZoomNet.IntegrationTests/TestsRunner.cs @@ -219,11 +219,6 @@ private async Task RunApiTestsAsync(IConnectionInfo connectionInfo, IWebPro private async Task RunWebSocketTestsAsync(string clientId, string clientSecret, string accountId, string subscriptionId, IWebProxy proxy) { - // Change the minimum logging level so we can see the traces from ZoomWebSocketClient - var config = NLog.LogManager.Configuration; - config.FindRuleByName("ColoredConsoleRule").EnableLoggingForLevel(NLog.LogLevel.Trace); - NLog.LogManager.Configuration = config; // Apply new config - var logger = _loggerFactory.CreateLogger(); var eventProcessor = new Func(async (webhookEvent, cancellationToken) => { diff --git a/Source/ZoomNet/Models/DataCenterRegion.cs b/Source/ZoomNet/Models/DataCenterRegion.cs index 299ccfcf..b5bcba9f 100644 --- a/Source/ZoomNet/Models/DataCenterRegion.cs +++ b/Source/ZoomNet/Models/DataCenterRegion.cs @@ -27,10 +27,6 @@ public enum DataCenterRegion [EnumMember(Value = "LA")] LatinAmerica, - /// Tokyo. - [EnumMember(Value = "TY")] - Tokyo, - /// China. [EnumMember(Value = "CN")] China, @@ -62,5 +58,183 @@ public enum DataCenterRegion /// Ireland. [EnumMember(Value = "IE")] Ireland, + + /// San Jose (SJ). + [EnumMember(Value = "SJ")] + SanJose1, + + /// San Jose (SC). + [EnumMember(Value = "SC")] + SanJose2, + + /// San Jose (SX). + [EnumMember(Value = "SX")] + SanJose3, + + /// San Jose (SJC). + [EnumMember(Value = "SJC")] + SanJose4, + + /// New York. + [EnumMember(Value = "NY")] + NewYork, + + /// Denver. + [EnumMember(Value = "DV")] + Denver, + + /// Virginia. + [EnumMember(Value = "IAD")] + Virginia, + + /// New Jersey. + [EnumMember(Value = "NX")] + NewJersey, + + /// Toronto (TR). + [EnumMember(Value = "TR")] + Toronto1, + + /// Toronto (YYZ). + [EnumMember(Value = "YYZ")] + Toronto2, + + /// Vancouver (VN). + [EnumMember(Value = "VN")] + Vancouver1, + + /// Vancouver (YVR). + [EnumMember(Value = "YVR")] + Vancouver2, + + /// Amsterdam (AM). + [EnumMember(Value = "AM")] + Amsterdam1, + + /// Amsterdam (AMS). + [EnumMember(Value = "AMS")] + Amsterdam2, + + /// Frankfurt (FR). + [EnumMember(Value = "FR")] + Frankfurt1, + + /// Frankfurt (FRA). + [EnumMember(Value = "FRA")] + Frankfurt2, + + /// Leipzig. + [EnumMember(Value = "LEJ")] + Leipzig, + + /// Zurich. + [EnumMember(Value = "ZRH")] + Zurich, + + /// Hong Kong (HK). + [EnumMember(Value = "HK")] + HongKong1, + + /// Hong Kong (HKG). + [EnumMember(Value = "HKG")] + HongKong2, + + /// Singapore (SG). + [EnumMember(Value = "SG")] + Singapore1, + + /// Singapore (SIN). + [EnumMember(Value = "SIN")] + Singapore2, + + /// Tokyo. + [EnumMember(Value = "TY")] + Tokyo, + + /// Narita. + [EnumMember(Value = "NRT")] + Narita, + + /// Osaka (OS). + [EnumMember(Value = "OS")] + Osaka1, + + /// Osaka (KIX). + [EnumMember(Value = "KIX")] + Osaka2, + + /// Sydney (SY). + [EnumMember(Value = "SY")] + Sydney1, + + /// Sydney (SYD). + [EnumMember(Value = "SYD")] + Sydney2, + + /// Melbourne (ME). + [EnumMember(Value = "ME")] + Melbourne1, + + /// Melbourne (MEL). + [EnumMember(Value = "MEL")] + Melbourne2, + + /// Mumbai (MB). + [EnumMember(Value = "MB")] + Mumbai1, + + /// Mumbai (BOM). + [EnumMember(Value = "BOM")] + Mumbai2, + + /// Hyderabad (HY). + [EnumMember(Value = "HY")] + Hyderabad1, + + /// Hyderabad (HYD). + [EnumMember(Value = "HYD")] + Hyderabad2, + + /// Tianjin. + [EnumMember(Value = "TJ")] + Tianjin, + + /// Sao Paulo. + [EnumMember(Value = "SP")] + SaoPaulo, + + /// Mexico (MX). + [EnumMember(Value = "MX")] + Mexico1, + + /// Mexico (QRO). + [EnumMember(Value = "QRO")] + Mexico2, + + /// Global Service Backup. + [EnumMember(Value = "GSB")] + GlobalServiceBackup, + + /// Cloud. + [EnumMember(Value = "Cloud")] + Cloud, + + /// Silicon Valley Gov. + [EnumMember(Value = "SV")] + SiliconValleyGov, + + /// New Jersey Gov. + [EnumMember(Value = "NJ")] + NewJerseyGov, + + /// Taiwan. + /// This is an undocumented value. See this GitHub issue for details. + [EnumMember(Value = "TW")] + Taiwan, + + /// Switzerland. + /// This is an undocumented value. See this GitHub issue for details. + [EnumMember(Value = "CH")] + Switzerland, } } diff --git a/Source/ZoomNet/Resources/IMeetings.cs b/Source/ZoomNet/Resources/IMeetings.cs index 71a0d0d1..361a758e 100644 --- a/Source/ZoomNet/Resources/IMeetings.cs +++ b/Source/ZoomNet/Resources/IMeetings.cs @@ -194,6 +194,14 @@ public interface IMeetings /// Task EndAsync(long meetingId, CancellationToken cancellationToken = default); + /// + /// Recover a deleted meeting. + /// + /// The meeting ID. + /// The cancellation token. + /// The async task. + Task RecoverAsync(long meetingId, CancellationToken cancellationToken = default); + /// /// List registrants of a meeting. /// diff --git a/Source/ZoomNet/Resources/Meetings.cs b/Source/ZoomNet/Resources/Meetings.cs index 82ccdc3f..2984d202 100644 --- a/Source/ZoomNet/Resources/Meetings.cs +++ b/Source/ZoomNet/Resources/Meetings.cs @@ -389,6 +389,26 @@ public Task EndAsync(long meetingId, CancellationToken cancellationToken = defau .AsMessage(); } + /// + /// Recover a deleted meeting. + /// + /// The meeting ID. + /// The cancellation token. + /// The async task. + public Task RecoverAsync(long meetingId, CancellationToken cancellationToken = default) + { + var data = new JsonObject + { + { "action", "recover" } + }; + + return _client + .PutAsync($"meetings/{meetingId}/status") + .WithJsonBody(data) + .WithCancellationToken(cancellationToken) + .AsMessage(); + } + /// /// List registrants of a meeting. /// @@ -893,7 +913,7 @@ public Task GetTemplatesAsync(string userId, CancellationToke /// public Task CreateInviteLinksAsync(long meetingId, IEnumerable names, long timeToLive = 7200, CancellationToken cancellationToken = default) { - if (names == null || !names.Any()) throw new ArgumentNullException("You must provide at least one name", nameof(names)); + if (names == null || !names.Any()) throw new ArgumentNullException(nameof(names), "You must provide at least one name"); var data = new JsonObject { @@ -1013,7 +1033,7 @@ public Task StopCloudRecordingAsync(long meetingId, CancellationToken cancellati /// public Task InviteParticipantsAsync(long meetingId, IEnumerable emailAddresses, CancellationToken cancellationToken = default) { - if (emailAddresses == null || !emailAddresses.Any()) throw new ArgumentNullException("You must provide at least one email address", nameof(emailAddresses)); + if (emailAddresses == null || !emailAddresses.Any()) throw new ArgumentNullException(nameof(emailAddresses), "You must provide at least one email address"); var data = new JsonObject {