diff --git a/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs b/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs index 1fb92a36..da5fa15d 100644 --- a/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs +++ b/Source/ZoomNet.IntegrationTests/Tests/Meetings.cs @@ -45,7 +45,9 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie { Audio = AudioType.Telephony, RegistrationType = RegistrationType.RegisterOnceAttendAll, - ApprovalType = ApprovalType.Manual + ApprovalType = ApprovalType.Manual, + JoinBeforeHost = true, + JoinBeforeHostTime = JoinBeforeHostTime.FiveMinutes, }; var trackingFields = new Dictionary() { @@ -240,6 +242,13 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie await client.Meetings.DeleteAsync(newRecurringMeeting.Id, null, false, false, cancellationToken).ConfigureAwait(false); await log.WriteLineAsync($"Recurring meeting {newRecurringMeeting.Id} deleted").ConfigureAwait(false); + + // Recurring meeting with no fixed time + var newRecurringNoFixTimeMeeting = await client.Meetings.CreateRecurringMeetingAsync(myUser.Id, "ZoomNet Integration Testing: recurring meeting with no fixed time", "The agenda", start, duration, null, TimeZones.UTC, "p@ss!w0rd", settings, null, null, cancellationToken).ConfigureAwait(false); + await log.WriteLineAsync($"Recurring meeting with no fixed time {newRecurringNoFixTimeMeeting.Id} created").ConfigureAwait(false); + + await client.Meetings.DeleteAsync(newRecurringNoFixTimeMeeting.Id, null, false, false, cancellationToken).ConfigureAwait(false); + await log.WriteLineAsync($"Recurring meeting with no fixed time {newRecurringNoFixTimeMeeting.Id} deleted").ConfigureAwait(false); } } } diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index e4e1cbf2..3fcdb4bb 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -628,6 +628,18 @@ internal static (WeakReference RequestReference, string Diag "code": 300, "message": "This meeting has not registration required: 544993922" } + + Sometimes, the JSON string contains additional info like this example: + { + "code":300, + "message":"Validation Failed.", + "errors":[ + { + "field":"settings.jbh_time", + "message":"Invalid parameter: jbh_time." + } + ] + } */ var responseContent = await message.Content.ReadAsStringAsync(null).ConfigureAwait(false); @@ -639,6 +651,21 @@ internal static (WeakReference RequestReference, string Diag var rootJsonElement = JsonDocument.Parse(responseContent).RootElement; errorCode = rootJsonElement.TryGetProperty("code", out JsonElement jsonErrorCode) ? (int?)jsonErrorCode.GetInt32() : (int?)null; errorMessage = rootJsonElement.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : (errorCode.HasValue ? $"Error code: {errorCode}" : errorMessage); + if (rootJsonElement.TryGetProperty("errors", out JsonElement jsonErrorDetails)) + { + var errorDetails = string.Join( + " ", + jsonErrorDetails + .EnumerateArray() + .Select(jsonErrorDetail => + { + var errorDetail = jsonErrorDetail.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : string.Empty; + return errorDetail; + }) + .Where(errorDetail => !string.IsNullOrEmpty(errorDetail))); + + if (!string.IsNullOrEmpty(errorDetails)) errorMessage += $" {errorDetails}"; + } isError = errorCode.HasValue; } diff --git a/Source/ZoomNet/Models/EncryptionType.cs b/Source/ZoomNet/Models/EncryptionType.cs index cad6d5df..8ee291a7 100644 --- a/Source/ZoomNet/Models/EncryptionType.cs +++ b/Source/ZoomNet/Models/EncryptionType.cs @@ -9,7 +9,7 @@ public enum EncryptionType { /// Enhanced encryption. /// Encryption data is stored in the cloud. - [EnumMember(Value = "enhanced_encryption ")] + [EnumMember(Value = "enhanced_encryption")] Enhanced, /// End-to-end encryption. diff --git a/Source/ZoomNet/Models/JoinBeforeHostTime.cs b/Source/ZoomNet/Models/JoinBeforeHostTime.cs new file mode 100644 index 00000000..2e86fc1e --- /dev/null +++ b/Source/ZoomNet/Models/JoinBeforeHostTime.cs @@ -0,0 +1,23 @@ +namespace ZoomNet.Models +{ + /// + /// Enumeration to indicate the time limits within which a participant can join a meeting before the meeting's host. + /// + public enum JoinBeforeHostTime + { + /// + /// Participants are allowed to join the meeting at anytime. + /// + Anytime = 0, + + /// + /// Participants are allowed to join the meeting 5 minutes before the meeting's start time. + /// + FiveMinutes = 5, + + /// + /// Participants are allowed to join the meeting 10 minutes before the meeting's start time. + /// + TenMinutes = 10, + } +} diff --git a/Source/ZoomNet/Models/MeetingSettings.cs b/Source/ZoomNet/Models/MeetingSettings.cs index 7f099482..ac8f2911 100644 --- a/Source/ZoomNet/Models/MeetingSettings.cs +++ b/Source/ZoomNet/Models/MeetingSettings.cs @@ -96,6 +96,13 @@ public class MeetingSettings [Obsolete("Deprecated")] public bool? HostInIndia { get; set; } + /// + /// Gets or sets the time limits within which a participant can join a meeting before the meeting's host. + /// + /// This value is applicable only if is true. + [JsonPropertyName("jbh_time")] + public JoinBeforeHostTime? JoinBeforeHostTime { get; set; } + /// /// Gets or sets the value indicating whether participants can join the meeting before the host starts the meeting. Only used for scheduled or recurring meetings. /// diff --git a/Source/ZoomNet/Resources/Meetings.cs b/Source/ZoomNet/Resources/Meetings.cs index f254e36f..11cddefb 100644 --- a/Source/ZoomNet/Resources/Meetings.cs +++ b/Source/ZoomNet/Resources/Meetings.cs @@ -194,7 +194,7 @@ public Task CreateRecurringMeetingAsync( { var data = new JsonObject { - { "type", start.HasValue ? 8 : 3 }, // 8 = Recurring with fixed time. 3 = Recurring with no fixed time. + { "type", recurrence == null ? MeetingType.RecurringNoFixedTime : MeetingType.RecurringFixedTime }, { "topic", topic }, { "password", password }, { "agenda", agenda },