Skip to content

Commit

Permalink
Merge branch 'release/0.84.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jericho committed Nov 15, 2024
2 parents ca7a7fc + fe69be5 commit 158b232
Show file tree
Hide file tree
Showing 51 changed files with 1,186 additions and 73 deletions.
2 changes: 1 addition & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"cake.tool": {
"version": "4.2.0",
"version": "5.0.0",
"commands": [
"dotnet-cake"
]
Expand Down
2 changes: 1 addition & 1 deletion Source/ZoomNet.IntegrationTests/TestSuites/ApiTestSuite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ internal class ApiTestSuite : TestSuite
typeof(Contacts),
typeof(Dashboards),
typeof(Meetings),
typeof(Reports),
typeof(Roles),
typeof(Users),
typeof(Webinars),
typeof(Reports),
};

public ApiTestSuite(IConnectionInfo connectionInfo, IWebProxy proxy, ILoggerFactory loggerFactory) :
Expand Down
25 changes: 14 additions & 11 deletions Source/ZoomNet.IntegrationTests/Tests/Accounts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie

await log.WriteLineAsync("\n***** ACCOUNTS *****\n").ConfigureAwait(false);

// GET ALL THE ACCOUNTS
var paginatedAccounts = await client.Accounts.GetAllAsync(100, null, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync($"There are {paginatedAccounts.TotalRecords} sub accounts under the main account").ConfigureAwait(false);

// GET SETTINGS
if (paginatedAccounts.Records.Any())
if (client.HasPermission("account:read:list_sub_accounts:master"))
{
var accountId = paginatedAccounts.Records.First().Id;
// GET ALL THE ACCOUNTS
var paginatedAccounts = await client.Accounts.GetAllAsync(100, null, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync($"There are {paginatedAccounts.TotalRecords} sub accounts under the main account").ConfigureAwait(false);

// GET SETTINGS
if (paginatedAccounts.Records.Any())
{
var accountId = paginatedAccounts.Records.First().Id;

var meetingAuthenticationSettings = await client.Accounts.GetMeetingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Meeting authentication settings retrieved").ConfigureAwait(false);
var meetingAuthenticationSettings = await client.Accounts.GetMeetingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Meeting authentication settings retrieved").ConfigureAwait(false);

var recordingAuthenticationSettings = await client.Accounts.GetRecordingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Recording authentication settings retrieved").ConfigureAwait(false);
var recordingAuthenticationSettings = await client.Accounts.GetRecordingAuthenticationSettingsAsync(accountId, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync("Recording authentication settings retrieved").ConfigureAwait(false);
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Source/ZoomNet.IntegrationTests/Tests/Meetings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public async Task RunAsync(User myUser, string[] myPermissions, IZoomClient clie
// Scheduled meeting
var start = DateTime.UtcNow.AddMonths(1);
var duration = 30;
var newScheduledMeeting = await client.Meetings.CreateScheduledMeetingAsync(myUser.Id, "ZoomNet Integration Testing: scheduled meeting", "The agenda", start, duration, TimeZones.UTC, "pass@word!", settings, trackingFields, null, true, false, cancellationToken).ConfigureAwait(false);
var newScheduledMeeting = await client.Meetings.CreateScheduledMeetingAsync(myUser.Id, "ZoomNet Integration Testing: scheduled meeting", "The agenda", start, duration, TimeZones.UTC, null, settings, trackingFields, null, true, false, cancellationToken).ConfigureAwait(false);
await log.WriteLineAsync($"Scheduled meeting {newScheduledMeeting.Id} created").ConfigureAwait(false);

var updatedSettings = new MeetingSettings() { Audio = AudioType.Voip };
Expand Down
52 changes: 46 additions & 6 deletions Source/ZoomNet.UnitTests/Extensions/InternalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,18 +327,18 @@ public class Enumconversion
public enum MyEnum
{
[EnumMember(Value = "One")]
First,
First = 1,

[MultipleValuesEnumMember(DefaultValue = "Two", OtherValues = new[] { "Second", "Alternative" })]
Second,
Second = 2,

[JsonPropertyName("Three")]
Third,
Third = 3,

[Description("Four")]
Fourth,
Fourth = 4,

Fifth
Fifth = 5
}

[Theory]
Expand Down Expand Up @@ -373,8 +373,31 @@ public void ToEnumString(MyEnum value, string expected)
result.ShouldBe(expected);

}
}

[Fact]
public void ThrowsWhenUndefinedValue()
{
// Arrange
var myInvalidEnumValue = (MyEnum)9999;

// Act
Should.Throw<ArgumentException>(() => myInvalidEnumValue.TryToEnumString(out string stringValue, true));
}

[Fact]
public void UndefinedValueCanBeIgnored()
{
// Arrange
var myInvalidEnumValue = (MyEnum)9999;

// Act
var result = myInvalidEnumValue.TryToEnumString(out string stringValue, false);

// Asert
result.ShouldBeFalse();
stringValue.ShouldBeNull();
}
}

public class GetErrorMessageAsync
{
Expand All @@ -394,6 +417,23 @@ public async Task CanHandleUnescapedDoubleQuotesInErrorMessage()
errorMessage.ShouldStartWith("Invalid access token, does not contain scopes");
errorCode.ShouldBe(104);
}

[Fact]
public async Task IncludesFieldNameInErrorMessage()
{
// Arrange
const string responseContent = @"{""code"":300,""message"":""Validation Failed."",""errors"":[{""field"":""settings.jbh_time"",""message"":""Invalid parameter: jbh_time.""}]}";
var message = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(responseContent) };
var response = new MockFluentHttpResponse(message, null, CancellationToken.None);

// Act
var (isError, errorMessage, errorCode) = await response.Message.GetErrorMessageAsync();

// Assert
isError.ShouldBeTrue();
errorMessage.ShouldBe("Validation Failed. settings.jbh_time Invalid parameter: jbh_time.");
errorCode.ShouldBe(300);
}
}
}
}
18 changes: 16 additions & 2 deletions Source/ZoomNet/Extensions/Internal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,12 @@ internal static DiagnosticInfo GetDiagnosticInfo(this IResponse response)
" ",
jsonErrorDetails
.EnumerateArray()
.Select(jsonErrorDetail => jsonErrorDetail.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : string.Empty)
.Select(jsonErrorDetail =>
{
var field = jsonErrorDetail.TryGetProperty("field", out JsonElement jsonField) ? jsonField.GetString() : string.Empty;
var message = jsonErrorDetail.TryGetProperty("message", out JsonElement jsonErrorMessage) ? jsonErrorMessage.GetString() : string.Empty;
return $"{field} {message}".Trim();
})
.Where(message => !string.IsNullOrEmpty(message)));

if (!string.IsNullOrEmpty(errorDetails)) errorMessage += $" {errorDetails}";
Expand Down Expand Up @@ -801,9 +806,18 @@ internal static string ToEnumString<T>(this T enumValue)
return enumValue.ToString();
}

internal static bool TryToEnumString<T>(this T enumValue, out string stringValue)
internal static bool TryToEnumString<T>(this T enumValue, out string stringValue, bool throwWhenUndefined = true)
where T : Enum
{
if (throwWhenUndefined)
{
var typeOfT = typeof(T);
if (!Enum.IsDefined(typeOfT, enumValue))
{
throw new ArgumentException($"{enumValue} is not a valid value for {typeOfT.Name}", nameof(enumValue));
}
}

var multipleValuesEnumMemberAttribute = enumValue.GetAttributeOfType<MultipleValuesEnumMemberAttribute>();
if (multipleValuesEnumMemberAttribute != null)
{
Expand Down
17 changes: 17 additions & 0 deletions Source/ZoomNet/Extensions/Public.cs
Original file line number Diff line number Diff line change
Expand Up @@ -368,5 +368,22 @@ public static async Task<string> AddUserToGroupAsync(this IGroups groupsResource
// We added a single member to a group therefore the array returned from the Zoom API contains a single element
return result.Single();
}

/// <summary>
/// Determines if the specified scope has been granted.
/// </summary>
/// <param name="client">The ZoomNet client.</param>
/// <param name="scope">The name of the scope.</param>
/// <returns>True if the scope has been granted, False otherwise.</returns>
/// <remarks>
/// The concept of "scopes" only applies to OAuth connections.
/// Therefore an exeption will be thrown if you invoke this method while using
/// a JWT connection (you shouldn't be using JWT in the first place since this
/// type of connection has been deprecated in the Zoom API since September 2023).
/// </remarks>
public static bool HasPermission(this IZoomClient client, string scope)
{
return client.HasPermissions(new[] { scope });
}
}
}
14 changes: 14 additions & 0 deletions Source/ZoomNet/IZoomClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using ZoomNet.Resources;

namespace ZoomNet
Expand Down Expand Up @@ -105,5 +106,18 @@ public interface IZoomClient
/// Gets the resource which allows you to manage webinars.
/// </summary>
IWebinars Webinars { get; }

/// <summary>
/// Determines if the specified scopes have been granted.
/// </summary>
/// <param name="scopes">The name of the scopes.</param>
/// <returns>True if all the scopes have been granted, False otherwise.</returns>
/// <remarks>
/// The concept of "scopes" only applies to OAuth connections.
/// Therefore an exeption will be thrown if you invoke this method while using
/// a JWT connection (you shouldn't be using JWT in the first place since this
/// type of connection has been deprecated in the Zoom API since September 2023).
/// </remarks>
bool HasPermissions(IEnumerable<string> scopes);
}
}
2 changes: 1 addition & 1 deletion Source/ZoomNet/Json/WebhookEventConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ private static KeyValuePair<string, object> ConvertJsonPropertyToKeyValuePair(Js
if (value.TryGetInt64(out var longValue)) return new KeyValuePair<string, object>(key, longValue);
if (value.TryGetInt32(out var intValue)) return new KeyValuePair<string, object>(key, intValue);
if (value.TryGetInt16(out var shortValue)) return new KeyValuePair<string, object>(key, shortValue);
throw new JsonException($"Property {key} appears to contain a numerical value but we are unable to determine to exact type");
throw new JsonException($"Property {key} appears to contain a numerical value but we are unable to determine the exact type");
default: return new KeyValuePair<string, object>(key, value.GetRawText());
}
}
Expand Down
42 changes: 41 additions & 1 deletion Source/ZoomNet/Json/ZoomNetJsonSerializerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.BatchRegistrant))]
[JsonSerializable(typeof(ZoomNet.Models.BatchRegistrantInfo))]
[JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType))]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType), TypeInfoPropertyName = "CallHandlingSettingsBusyOnAnotherCallActionType")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings), TypeInfoPropertyName = "CallHandlingSettingsCallDistributionSettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingChildSubsettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingSubsettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSettingType")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsBase")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsType")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType), TypeInfoPropertyName = "CallHandlingSettingsCallNotAnswerActionType")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursChildSubsettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursSubsettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursType")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact), TypeInfoPropertyName = "CallHandlingSettingsExternalContact")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings), TypeInfoPropertyName = "CallHandlingSettingsHolidaySubsettings")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType), TypeInfoPropertyName = "CallHandlingSettingsRingModeType")]
[JsonSerializable(typeof(ZoomNet.Models.CallingPlan))]
[JsonSerializable(typeof(ZoomNet.Models.CallingPlanType))]
[JsonSerializable(typeof(ZoomNet.Models.CallLog))]
Expand Down Expand Up @@ -84,6 +99,8 @@ 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.DailyUsageSummary))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetrics))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetricsPaginationObject))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingParticipant))]
Expand Down Expand Up @@ -166,6 +183,7 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.PollStatus))]
[JsonSerializable(typeof(ZoomNet.Models.PollType))]
[JsonSerializable(typeof(ZoomNet.Models.PresenceStatus))]
[JsonSerializable(typeof(ZoomNet.Models.PresenceStatusResponse))]
[JsonSerializable(typeof(ZoomNet.Models.PronounDisplayType))]
[JsonSerializable(typeof(ZoomNet.Models.PurchasingTimeFrame))]
[JsonSerializable(typeof(ZoomNet.Models.QualityOfService.CpuUsage), TypeInfoPropertyName = "QualityOfServiceCpuUsage")]
Expand Down Expand Up @@ -323,6 +341,21 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.BatchRegistrant[]))]
[JsonSerializable(typeof(ZoomNet.Models.BatchRegistrantInfo[]))]
[JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType[]))]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType[]), TypeInfoPropertyName = "CallHandlingSettingsBusyOnAnotherCallActionTypeArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallDistributionSettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallDistributionSettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingChildSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingChildSubsettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallForwardingSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallForwardingSubsettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSettingTypeArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsBase[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsBaseArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType[]), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsTypeArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType[]), TypeInfoPropertyName = "CallHandlingSettingsCallNotAnswerActionTypeArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursChildSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursChildSubsettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursSubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursSubsettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType[]), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursTypeArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.ExternalContact[]), TypeInfoPropertyName = "CallHandlingSettingsExternalContactArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.HolidaySubsettings[]), TypeInfoPropertyName = "CallHandlingSettingsHolidaySubsettingsArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType[]), TypeInfoPropertyName = "CallHandlingSettingsRingModeTypeArray")]
[JsonSerializable(typeof(ZoomNet.Models.CallingPlan[]))]
[JsonSerializable(typeof(ZoomNet.Models.CallingPlanType[]))]
[JsonSerializable(typeof(ZoomNet.Models.CallLog[]))]
Expand Down Expand Up @@ -386,6 +419,7 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.CrcPortsUsage[]))]
[JsonSerializable(typeof(ZoomNet.Models.CustomAttribute[]))]
[JsonSerializable(typeof(ZoomNet.Models.DailyUsageReport[]))]
[JsonSerializable(typeof(ZoomNet.Models.DailyUsageSummary[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetrics[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingMetricsPaginationObject[]))]
[JsonSerializable(typeof(ZoomNet.Models.DashboardMeetingParticipant[]))]
Expand All @@ -395,7 +429,6 @@ 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[]))]
Expand Down Expand Up @@ -469,6 +502,7 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.PollStatus[]))]
[JsonSerializable(typeof(ZoomNet.Models.PollType[]))]
[JsonSerializable(typeof(ZoomNet.Models.PresenceStatus[]))]
[JsonSerializable(typeof(ZoomNet.Models.PresenceStatusResponse[]))]
[JsonSerializable(typeof(ZoomNet.Models.PronounDisplayType[]))]
[JsonSerializable(typeof(ZoomNet.Models.PurchasingTimeFrame[]))]
[JsonSerializable(typeof(ZoomNet.Models.QualityOfService.CpuUsage[]), TypeInfoPropertyName = "QualityOfServiceCpuUsageArray")]
Expand Down Expand Up @@ -616,6 +650,12 @@ namespace ZoomNet.Json
[JsonSerializable(typeof(ZoomNet.Models.AuthenticationType?))]
[JsonSerializable(typeof(ZoomNet.Models.AutoRecordingType?))]
[JsonSerializable(typeof(ZoomNet.Models.CalendarIntegrationType?))]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.BusyOnAnotherCallActionType?), TypeInfoPropertyName = "CallHandlingSettingsBusyOnAnotherCallActionTypeNullable")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSettingType?), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSettingTypeNullable")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallHandlingSubsettingsType?), TypeInfoPropertyName = "CallHandlingSettingsCallHandlingSubsettingsTypeNullable")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CallNotAnswerActionType?), TypeInfoPropertyName = "CallHandlingSettingsCallNotAnswerActionTypeNullable")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.CustomHoursType?), TypeInfoPropertyName = "CallHandlingSettingsCustomHoursTypeNullable")]
[JsonSerializable(typeof(ZoomNet.Models.CallHandlingSettings.RingModeType?), TypeInfoPropertyName = "CallHandlingSettingsRingModeTypeNullable")]
[JsonSerializable(typeof(ZoomNet.Models.CallingPlanType?))]
[JsonSerializable(typeof(ZoomNet.Models.CallLogCalleeNumberType?))]
[JsonSerializable(typeof(ZoomNet.Models.CallLogCallerNumberType?))]
Expand Down
Loading

0 comments on commit 158b232

Please sign in to comment.