diff --git a/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj b/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj
index fc5d1247..8b253ed3 100644
--- a/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj
+++ b/Source/ZoomNet.IntegrationTests/ZoomNet.IntegrationTests.csproj
@@ -14,10 +14,10 @@
-
-
-
-
+
+
+
+
diff --git a/Source/ZoomNet.UnitTests/Models/DailyUsageReportTests.cs b/Source/ZoomNet.UnitTests/Models/DailyUsageReportTests.cs
new file mode 100644
index 00000000..0cd29319
--- /dev/null
+++ b/Source/ZoomNet.UnitTests/Models/DailyUsageReportTests.cs
@@ -0,0 +1,53 @@
+using Shouldly;
+using System.Text.Json;
+using Xunit;
+using ZoomNet.Json;
+using ZoomNet.Models;
+
+namespace ZoomNet.UnitTests.Models
+{
+ public class DailyUsageReportTests
+ {
+ #region FIELDS
+
+ internal const string DAILY_USAGE_REPORT_JSON = @"{
+ ""dates"": [
+ {
+ ""date"": ""2022-03-01"",
+ ""meeting_minutes"": 34,
+ ""meetings"": 2,
+ ""new_users"": 3,
+ ""participants"": 4
+ }
+ ],
+ ""month"": 3,
+ ""year"": 2022
+ }";
+
+ #endregion
+
+ [Fact]
+ public void Parse_json()
+ {
+ // Arrange
+
+ // Act
+ var result = JsonSerializer.Deserialize(DAILY_USAGE_REPORT_JSON, JsonFormatter.SerializerOptions);
+
+ // Assert
+ result.ShouldNotBeNull();
+ result.Month.ShouldBe(3);
+ result.Year.ShouldBe(2022);
+ result.DailyUsageSummaries.ShouldNotBeEmpty();
+ result.DailyUsageSummaries.Length.ShouldBe(1);
+ result.DailyUsageSummaries[0].Date.Year.ShouldBe(2022);
+ result.DailyUsageSummaries[0].Date.Month.ShouldBe(3);
+ result.DailyUsageSummaries[0].Date.Day.ShouldBe(1);
+ result.DailyUsageSummaries[0].MeetingMinutes.ShouldBe(34);
+ result.DailyUsageSummaries[0].Meetings.ShouldBe(2);
+ result.DailyUsageSummaries[0].NewUsers.ShouldBe(3);
+ result.DailyUsageSummaries[0].Participants.ShouldBe(4);
+
+ }
+ }
+}
diff --git a/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs b/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs
index 2f9a0fd2..b7f77ebc 100644
--- a/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs
+++ b/Source/ZoomNet.UnitTests/Resources/CloudRecordingsTests.cs
@@ -552,7 +552,7 @@ public async Task DownloadFileAsync_with_expired_token()
var mockHttp = new MockHttpMessageHandler();
mockHttp // The first time the file is requested, we return "401 Unauthorized" to simulate an expired token.
.Expect(HttpMethod.Get, downloadUrl)
- .Respond(HttpStatusCode.Unauthorized, new StringContent("{\"message\":\"access token is expired\"}"));
+ .Respond(HttpStatusCode.Unauthorized, new StringContent("{\"code\":123456,\"message\":\"access token is expired\"}"));
mockHttp // The second time the file is requested, we return "200 OK" with the file content.
.Expect(HttpMethod.Get, downloadUrl)
.Respond(HttpStatusCode.OK, new StringContent("This is the content of the file"));
diff --git a/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj b/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj
index 136ecd88..90825cf2 100644
--- a/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj
+++ b/Source/ZoomNet.UnitTests/ZoomNet.UnitTests.csproj
@@ -12,7 +12,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
@@ -20,7 +20,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs
index ab169d60..7a3c152f 100644
--- a/Source/ZoomNet/Extensions/Internal.cs
+++ b/Source/ZoomNet/Extensions/Internal.cs
@@ -16,6 +16,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ZoomNet.Json;
@@ -347,19 +348,19 @@ internal static async Task> AsPaginate
return await response.AsPaginatedResponseWithTokenAndDateRange(propertyName, options).ConfigureAwait(false);
}
- /// Get a raw JSON document representation of the response.
+ /// Get a JSON representation of the response.
/// An error occurred processing the response.
- internal static Task AsRawJsonDocument(this IResponse response, string propertyName = null, bool throwIfPropertyIsMissing = true)
+ internal static Task AsJson(this IResponse response, string propertyName = null, bool throwIfPropertyIsMissing = true)
{
- return response.Message.Content.AsRawJsonDocument(propertyName, throwIfPropertyIsMissing);
+ return response.Message.Content.AsJson(propertyName, throwIfPropertyIsMissing);
}
- /// Get a raw JSON document representation of the response.
+ /// Get a JSON representation of the response.
/// An error occurred processing the response.
- internal static async Task AsRawJsonDocument(this IRequest request, string propertyName = null, bool throwIfPropertyIsMissing = true)
+ internal static async Task AsJson(this IRequest request, string propertyName = null, bool throwIfPropertyIsMissing = true)
{
var response = await request.AsResponse().ConfigureAwait(false);
- return await response.AsRawJsonDocument(propertyName, throwIfPropertyIsMissing).ConfigureAwait(false);
+ return await response.AsJson(propertyName, throwIfPropertyIsMissing).ConfigureAwait(false);
}
///
@@ -734,41 +735,31 @@ internal static DiagnosticInfo GetDiagnosticInfo(this IResponse response)
}
*/
- var responseContent = await message.Content.ReadAsStringAsync(null).ConfigureAwait(false);
-
- if (!string.IsNullOrEmpty(responseContent))
+ try
{
- try
+ var jsonResponse = await message.Content.ParseZoomResponseAsync().ConfigureAwait(false);
+ if (jsonResponse.ValueKind == JsonValueKind.Object)
{
- var rootJsonElement = JsonDocument.Parse(responseContent).RootElement;
-
- if (rootJsonElement.ValueKind == JsonValueKind.Object)
+ errorCode = jsonResponse.TryGetProperty("code", out JsonElement jsonErrorCode) ? jsonErrorCode.GetInt32() : null;
+ errorMessage = jsonResponse.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : errorCode.HasValue ? $"Error code: {errorCode}" : errorMessage;
+ if (jsonResponse.TryGetProperty("errors", out JsonElement jsonErrorDetails))
{
- errorCode = rootJsonElement.TryGetProperty("code", out JsonElement jsonErrorCode) ? jsonErrorCode.GetInt32() : 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(message => !string.IsNullOrEmpty(message)));
-
- if (!string.IsNullOrEmpty(errorDetails)) errorMessage += $" {errorDetails}";
- }
-
- return (errorCode.HasValue, errorMessage, errorCode);
+ var errorDetails = string.Join(
+ " ",
+ jsonErrorDetails
+ .EnumerateArray()
+ .Select(jsonErrorDetail => jsonErrorDetail.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : string.Empty)
+ .Where(message => !string.IsNullOrEmpty(message)));
+
+ if (!string.IsNullOrEmpty(errorDetails)) errorMessage += $" {errorDetails}";
}
+
+ return (errorCode.HasValue, errorMessage, errorCode);
}
- catch
- {
- // Intentionally ignore parsing errors
- }
+ }
+ catch
+ {
+ // Intentionally ignore parsing errors
}
return (!message.IsSuccessStatusCode, errorMessage, errorCode);
@@ -951,6 +942,34 @@ internal static bool IsNullableType(this Type type)
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
+ private static async Task ParseZoomResponseAsync(this HttpContent responseFromZoomApi, CancellationToken cancellationToken = default)
+ {
+ var responseContent = await responseFromZoomApi.ReadAsStringAsync(null, cancellationToken).ConfigureAwait(false);
+ if (string.IsNullOrEmpty(responseContent)) return default; // the 'ValueKind' property of the default JsonElement is JsonValueKind.Undefined
+
+ const string pattern = @"(.*?)(?<=""message"":"")(.*?)(?=""})(.*?$)";
+ var matches = Regex.Match(responseContent, pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled);
+ var prefix = matches.Groups[1].Value;
+ var message = matches.Groups[2].Value;
+ var postfix = matches.Groups[3].Value;
+
+ if (string.IsNullOrEmpty(message)) return JsonDocument.Parse(responseContent).RootElement;
+
+ /*
+ Sometimes the error message is malformed due to the presence of double quotes that are not properly escaped.
+ See: https://devforum.zoom.us/t/list-events-endpoint-returns-invalid-json-in-the-payload/115792 for more info.
+ One instance where this problem was observed is when retrieving the list of events without having the necessary permissions to do so.
+ The result is the following response with unescaped double-quotes in the error message:
+ {
+ "code": 104,
+ "message": "Invalid access token, does not contain scopes:["zoom_events_basic:read","zoom_events_basic:read:admin"]"
+ }
+ */
+ var escapedMessage = Regex.Replace(message, @"(?Asynchronously converts the JSON encoded content and convert it to an object of the desired type.
/// The response model to deserialize into.
/// The content.
@@ -984,28 +1003,30 @@ private static async Task AsObject(this HttpContent httpContent, string pr
}
}
- /// Get a raw JSON object representation of the response.
+ /// Get a JSON representation of the response.
/// The content.
/// The name of the JSON property (or null if not applicable) where the desired data is stored.
/// Indicates if an exception should be thrown when the specified JSON property is missing from the response.
/// The cancellation token.
- /// Returns the response body, or null if the response has no body.
+ /// Returns the response body, or a JsonElement with its 'ValueKind' set to 'Undefined' if the response has no body.
/// An error occurred processing the response.
- private static async Task AsRawJsonDocument(this HttpContent httpContent, string propertyName = null, bool throwIfPropertyIsMissing = true, CancellationToken cancellationToken = default)
+ private static async Task AsJson(this HttpContent httpContent, string propertyName = null, bool throwIfPropertyIsMissing = true, CancellationToken cancellationToken = default)
{
- var responseContent = await httpContent.ReadAsStringAsync(null, cancellationToken).ConfigureAwait(false);
-
- var jsonDoc = JsonDocument.Parse(responseContent, default);
+ var jsonResponse = await httpContent.ParseZoomResponseAsync(cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(propertyName))
{
- return jsonDoc;
+ return jsonResponse;
}
- if (jsonDoc.RootElement.TryGetProperty(propertyName, out JsonElement property))
+ if (jsonResponse.ValueKind != JsonValueKind.Object)
+ {
+ throw new Exception("The response from the Zomm API does not contain a valid JSON string");
+ }
+ else if (jsonResponse.TryGetProperty(propertyName, out JsonElement property))
{
var propertyContent = property.GetRawText();
- return JsonDocument.Parse(propertyContent, default);
+ return JsonDocument.Parse(propertyContent, default).RootElement;
}
else if (throwIfPropertyIsMissing)
{
@@ -1027,9 +1048,8 @@ private static async Task AsRawJsonDocument(this HttpContent httpC
/// An error occurred processing the response.
private static async Task> AsPaginatedResponse(this HttpContent httpContent, string propertyName, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
{
- // Get the content as a queryable json document
- var doc = await httpContent.AsRawJsonDocument(null, false, cancellationToken).ConfigureAwait(false);
- var rootElement = doc.RootElement;
+ // Get the content as a JSON element
+ var rootElement = await httpContent.AsJson(null, false, cancellationToken).ConfigureAwait(false);
// Get the various metadata properties
var pageCount = rootElement.GetPropertyValue("page_count", 0);
@@ -1068,9 +1088,8 @@ private static async Task> AsPaginatedResponse(this Http
/// An error occurred processing the response.
private static async Task> AsPaginatedResponseWithToken(this HttpContent httpContent, string propertyName, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
{
- // Get the content as a queryable json document
- var doc = await httpContent.AsRawJsonDocument(null, false, cancellationToken).ConfigureAwait(false);
- var rootElement = doc.RootElement;
+ // Get the content as a JSON element
+ var rootElement = await httpContent.AsJson(null, false, cancellationToken).ConfigureAwait(false);
// Get the various metadata properties
var nextPageToken = rootElement.GetPropertyValue("next_page_token", string.Empty);
@@ -1107,9 +1126,8 @@ private static async Task> AsPaginatedResponseWith
/// An error occurred processing the response.
private static async Task> AsPaginatedResponseWithTokenAndDateRange(this HttpContent httpContent, string propertyName, JsonSerializerOptions options = null, CancellationToken cancellationToken = default)
{
- // Get the content as a queryable json document
- var doc = await httpContent.AsRawJsonDocument(null, false, cancellationToken).ConfigureAwait(false);
- var rootElement = doc.RootElement;
+ // Get the content as a JSON element
+ var rootElement = await httpContent.AsJson(null, false, cancellationToken).ConfigureAwait(false);
// Get the various metadata properties
var from = DateTime.ParseExact(rootElement.GetPropertyValue("from", string.Empty), "yyyy-MM-dd", CultureInfo.InvariantCulture);
@@ -1150,7 +1168,11 @@ private static T GetPropertyValue(this JsonElement element, string[] names, T
if (property.HasValue) break;
}
- if (!property.HasValue) return defaultValue;
+ if (!property.HasValue)
+ {
+ if (throwIfMissing) throw new Exception($"Unable to find {string.Join(", ", names)} in the Json document");
+ else return defaultValue;
+ }
var typeOfT = typeof(T);
diff --git a/Source/ZoomNet/Json/DateOnlyConverter.cs b/Source/ZoomNet/Json/DateOnlyConverter.cs
new file mode 100644
index 00000000..96570405
--- /dev/null
+++ b/Source/ZoomNet/Json/DateOnlyConverter.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Text.Json;
+
+namespace ZoomNet.Json
+{
+ ///
+ /// Converts a DateOnly (which is represented by 3 integer values: year, month and day) to or from JSON.
+ ///
+ ///
+ internal class DateOnlyConverter : ZoomNetJsonConverter<(int Year, int Month, int Day)>
+ {
+ public override (int Year, int Month, int Day) Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.None:
+ case JsonTokenType.Null:
+ case JsonTokenType.String when string.IsNullOrEmpty(reader.GetString()):
+ throw new JsonException("Unable to convert a null value to DateOnly");
+
+ case JsonTokenType.String:
+ var rawValue = reader.GetString();
+ var parts = rawValue.Split('-');
+ if (parts.Length != 3) throw new JsonException($"Unable to convert {rawValue} to DateOnly");
+ return (int.Parse(parts[0]), int.Parse(parts[1]), int.Parse(parts[2]));
+
+ default:
+ throw new JsonException($"Unable to convert {reader.TokenType.ToEnumString()} to DateOnly");
+ }
+ }
+
+ public override void Write(Utf8JsonWriter writer, (int Year, int Month, int Day) value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue($"{value.Year:D4}-{value.Month:D2}-{value.Day:D2}");
+ }
+ }
+}
diff --git a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs
index 42f4c6df..0c70f0cb 100644
--- a/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs
+++ b/Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs
@@ -385,6 +385,7 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.CrcPortsHourUsage[]))]
[JsonSerializable(typeof(ZoomNet.Models.CrcPortsUsage[]))]
[JsonSerializable(typeof(ZoomNet.Models.CustomAttribute[]))]
+ [JsonSerializable(typeof(ZoomNet.Models.DailyUsageReport[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetrics[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetricsPaginationObject[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingParticipant[]))]
@@ -394,6 +395,7 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.DashboardParticipant[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardParticipantQos[]))]
[JsonSerializable(typeof(ZoomNet.Models.DataCenterRegion[]))]
+ [JsonSerializable(typeof(ZoomNet.Models.DailyUsageSummary[]))]
[JsonSerializable(typeof(ZoomNet.Models.EmailNotificationUserSettings[]))]
[JsonSerializable(typeof(ZoomNet.Models.EmergencyAddress[]))]
[JsonSerializable(typeof(ZoomNet.Models.EncryptionType[]))]
diff --git a/Source/ZoomNet/Models/DailyUsageReport.cs b/Source/ZoomNet/Models/DailyUsageReport.cs
new file mode 100644
index 00000000..31277b67
--- /dev/null
+++ b/Source/ZoomNet/Models/DailyUsageReport.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace ZoomNet.Models
+{
+ ///
+ /// Daily Usage Report.
+ ///
+ public class DailyUsageReport
+ {
+ /// Gets or sets the daily usage summaries.
+ [JsonPropertyName("dates")]
+ public DailyUsageSummary[] DailyUsageSummaries { get; set; }
+
+ /// Gets or sets the month.
+ [JsonPropertyName("month")]
+ public int Month { get; set; }
+
+ /// Gets or sets the year.
+ [JsonPropertyName("year")]
+ public int Year { get; set; }
+ }
+}
diff --git a/Source/ZoomNet/Models/DailyUsageSummary.cs b/Source/ZoomNet/Models/DailyUsageSummary.cs
new file mode 100644
index 00000000..1bff91ab
--- /dev/null
+++ b/Source/ZoomNet/Models/DailyUsageSummary.cs
@@ -0,0 +1,32 @@
+using System.Text.Json.Serialization;
+using ZoomNet.Json;
+
+namespace ZoomNet.Models
+{
+ ///
+ /// Date Object.
+ ///
+ public class DailyUsageSummary
+ {
+ /// Gets or sets the date.
+ [JsonPropertyName("date")]
+ [JsonConverter(typeof(DateOnlyConverter))]
+ public (int Year, int Month, int Day) Date { get; set; }
+
+ /// Gets or sets number of meeting minutes.
+ [JsonPropertyName("meeting_minutes")]
+ public int MeetingMinutes { get; set; }
+
+ /// Gets or sets number of meetings.
+ [JsonPropertyName("meetings")]
+ public int Meetings { get; set; }
+
+ /// Gets or sets number of new users.
+ [JsonPropertyName("new_users")]
+ public int NewUsers { get; set; }
+
+ /// Gets or sets number of participants.
+ [JsonPropertyName("participants")]
+ public int Participants { get; set; }
+ }
+}
diff --git a/Source/ZoomNet/Resources/Accounts.cs b/Source/ZoomNet/Resources/Accounts.cs
index 63b72b58..90ba601b 100644
--- a/Source/ZoomNet/Resources/Accounts.cs
+++ b/Source/ZoomNet/Resources/Accounts.cs
@@ -203,13 +203,13 @@ public async Task GetMeetingAuthenticationSettingsAsync(
.GetAsync($"accounts/{accountId}/settings")
.WithArgument("option", "meeting_authentication")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument()
+ .AsJson()
.ConfigureAwait(false);
var settings = new AuthenticationSettings()
{
- RequireAuthentication = response.RootElement.GetPropertyValue("meeting_authentication", false),
- AuthenticationOptions = response.RootElement.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
+ RequireAuthentication = response.GetPropertyValue("meeting_authentication", false),
+ AuthenticationOptions = response.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
};
return settings;
@@ -229,13 +229,13 @@ public async Task GetRecordingAuthenticationSettingsAsyn
.GetAsync($"accounts/{accountId}/settings")
.WithArgument("option", "recording_authentication")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument()
+ .AsJson()
.ConfigureAwait(false);
var settings = new AuthenticationSettings()
{
- RequireAuthentication = response.RootElement.GetPropertyValue("recording_authentication", false),
- AuthenticationOptions = response.RootElement.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
+ RequireAuthentication = response.GetPropertyValue("recording_authentication", false),
+ AuthenticationOptions = response.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
};
return settings;
@@ -254,10 +254,10 @@ public async Task GetRecordingAuthenticationSettingsAsyn
var response = await _client
.GetAsync($"accounts/{accountId}/managed_domains")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument("domains")
+ .AsJson("domains")
.ConfigureAwait(false);
- var managedDomains = response.RootElement
+ var managedDomains = response
.EnumerateArray()
.Select(jsonElement =>
{
diff --git a/Source/ZoomNet/Resources/IReports.cs b/Source/ZoomNet/Resources/IReports.cs
index 965641e6..2e324700 100644
--- a/Source/ZoomNet/Resources/IReports.cs
+++ b/Source/ZoomNet/Resources/IReports.cs
@@ -87,5 +87,17 @@ public interface IReports
/// An array of report items.
///
Task> GetHostsAsync(DateTime from, DateTime to, ReportHostType type = ReportHostType.Active, int pageSize = 30, string pageToken = null, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets daily report to access the account-wide usage of Zoom services for each day in a given month. It lists the number of new users, meetings, participants, and meeting minutes.
+ ///
+ /// Year for this report.
+ /// Month for this report.
+ /// The group ID.
+ /// The cancellation token.
+ ///
+ /// The object of .
+ ///
+ public Task GetDailyUsageReportAsync(int year, int month, string groupId = null, CancellationToken cancellationToken = default);
}
}
diff --git a/Source/ZoomNet/Resources/Meetings.cs b/Source/ZoomNet/Resources/Meetings.cs
index 0c51d9cc..118fbb29 100644
--- a/Source/ZoomNet/Resources/Meetings.cs
+++ b/Source/ZoomNet/Resources/Meetings.cs
@@ -795,10 +795,10 @@ public async Task GetRegistrationQuestionsAsync
var response = await _client
.GetAsync($"meetings/{meetingId}/registrants/questions")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument()
+ .AsJson()
.ConfigureAwait(false);
- var allFields = response.RootElement.GetProperty("questions").EnumerateArray()
+ var allFields = response.GetProperty("questions").EnumerateArray()
.Select(item => (Field: item.GetPropertyValue("field_name").ToEnum(), IsRequired: item.GetPropertyValue("required")));
var requiredFields = allFields.Where(f => f.IsRequired).Select(f => f.Field).ToArray();
@@ -808,7 +808,7 @@ public async Task GetRegistrationQuestionsAsync
{
RequiredFields = requiredFields,
OptionalFields = optionalFields,
- Questions = response.RootElement.GetProperty("custom_questions", false)?.ToObject() ?? Array.Empty()
+ Questions = response.GetProperty("custom_questions", false)?.ToObject() ?? Array.Empty()
};
return registrationQuestions;
}
diff --git a/Source/ZoomNet/Resources/Reports.cs b/Source/ZoomNet/Resources/Reports.cs
index b8dd9ea2..24ab520f 100644
--- a/Source/ZoomNet/Resources/Reports.cs
+++ b/Source/ZoomNet/Resources/Reports.cs
@@ -87,6 +87,17 @@ public Task> GetHostsAsync(DateTime from,
.AsPaginatedResponseWithToken("users");
}
+ ///
+ public Task GetDailyUsageReportAsync(int year, int month, string groupId = null, CancellationToken cancellationToken = default)
+ {
+ return _client.GetAsync("report/daily")
+ .WithArgument("year", year)
+ .WithArgument("month", month)
+ .WithArgument("groupId", groupId)
+ .WithCancellationToken(cancellationToken)
+ .AsObject();
+ }
+
private static void VerifyPageSize(int pageSize)
{
if (pageSize < 1 || pageSize > 300)
diff --git a/Source/ZoomNet/Resources/Users.cs b/Source/ZoomNet/Resources/Users.cs
index 2e0c878d..23627219 100644
--- a/Source/ZoomNet/Resources/Users.cs
+++ b/Source/ZoomNet/Resources/Users.cs
@@ -445,13 +445,13 @@ public async Task GetMeetingAuthenticationSettingsAsync(
.GetAsync($"users/{userId}/settings")
.WithArgument("option", "meeting_authentication")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument()
+ .AsJson()
.ConfigureAwait(false);
var settings = new AuthenticationSettings()
{
- RequireAuthentication = response.RootElement.GetPropertyValue("meeting_authentication", false),
- AuthenticationOptions = response.RootElement.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
+ RequireAuthentication = response.GetPropertyValue("meeting_authentication", false),
+ AuthenticationOptions = response.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
};
return settings;
@@ -471,13 +471,13 @@ public async Task GetRecordingAuthenticationSettingsAsyn
.GetAsync($"users/{userId}/settings")
.WithArgument("option", "recording_authentication")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument()
+ .AsJson()
.ConfigureAwait(false);
var settings = new AuthenticationSettings()
{
- RequireAuthentication = response.RootElement.GetPropertyValue("recording_authentication", false),
- AuthenticationOptions = response.RootElement.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
+ RequireAuthentication = response.GetPropertyValue("recording_authentication", false),
+ AuthenticationOptions = response.GetProperty("authentication_options", false)?.ToObject() ?? Array.Empty()
};
return settings;
@@ -718,7 +718,7 @@ public Task DeleteVirtualBackgroundAsync(string userId, string fileId, Cancellat
///
public Task UpdatePresenceStatusAsync(string userId, PresenceStatus status, int? duration = null, CancellationToken cancellationToken = default)
{
- if (status == PresenceStatus.Unknown) throw new ArgumentOutOfRangeException("You can not change a user's status to Unknown.", nameof(status));
+ if (status == PresenceStatus.Unknown) throw new ArgumentOutOfRangeException(nameof(status), "You can not change a user's status to Unknown.");
var data = new JsonObject
{
diff --git a/Source/ZoomNet/Resources/Webinars.cs b/Source/ZoomNet/Resources/Webinars.cs
index efe88e22..54c3b8e0 100644
--- a/Source/ZoomNet/Resources/Webinars.cs
+++ b/Source/ZoomNet/Resources/Webinars.cs
@@ -800,10 +800,10 @@ public async Task GetRegistrationQuestionsAsync
var response = await _client
.GetAsync($"webinars/{webinarId}/registrants/questions")
.WithCancellationToken(cancellationToken)
- .AsRawJsonDocument()
+ .AsJson()
.ConfigureAwait(false);
- var allFields = response.RootElement.GetProperty("questions").EnumerateArray()
+ var allFields = response.GetProperty("questions").EnumerateArray()
.Select(item => (Field: item.GetPropertyValue("field_name").ToEnum(), IsRequired: item.GetPropertyValue("required")));
var requiredFields = allFields.Where(f => f.IsRequired).Select(f => f.Field).ToArray();
@@ -813,7 +813,7 @@ public async Task GetRegistrationQuestionsAsync
{
RequiredFields = requiredFields,
OptionalFields = optionalFields,
- Questions = response.RootElement.GetProperty("custom_questions", false)?.ToObject() ?? Array.Empty()
+ Questions = response.GetProperty("custom_questions", false)?.ToObject() ?? Array.Empty()
};
return registrationQuestions;
}
diff --git a/Source/ZoomNet/Utilities/ZoomRetryCoordinator.cs b/Source/ZoomNet/Utilities/ZoomRetryCoordinator.cs
index 29dc87c0..8947b3c8 100644
--- a/Source/ZoomNet/Utilities/ZoomRetryCoordinator.cs
+++ b/Source/ZoomNet/Utilities/ZoomRetryCoordinator.cs
@@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -66,10 +65,8 @@ public async Task ExecuteAsync(IRequest request, Func
-
+
-
+
diff --git a/build.cake b/build.cake
index f5742da2..d3c1785b 100644
--- a/build.cake
+++ b/build.cake
@@ -1,9 +1,9 @@
// Install tools.
-#tool dotnet:?package=GitVersion.Tool&version=6.0.1
+#tool dotnet:?package=GitVersion.Tool&version=6.0.2
#tool dotnet:?package=coveralls.net&version=4.0.1
#tool nuget:https://f.feedz.io/jericho/jericho/nuget/?package=GitReleaseManager&version=0.17.0-collaborators0008
-#tool nuget:?package=ReportGenerator&version=5.3.8
-#tool nuget:?package=xunit.runner.console&version=2.9.0
+#tool nuget:?package=ReportGenerator&version=5.3.9
+#tool nuget:?package=xunit.runner.console&version=2.9.1
#tool nuget:?package=CodecovUploader&version=0.8.0
// Install addins.
diff --git a/global.json b/global.json
index 175f404f..caa3335a 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.303",
+ "version": "8.0.402",
"rollForward": "patch",
"allowPrerelease": false
}