From e1e04ec8af1da4a758c1c44b90fd730f9042ce89 Mon Sep 17 00:00:00 2001 From: Jericho Date: Fri, 15 Jul 2022 10:54:47 -0400 Subject: [PATCH 1/4] (GH-228) The meeting unique identifier in webhook JSON sometimes contains a string and sometimes an integer. This change allows the JSON parser to accept either data type. --- Source/ZoomNet/Models/Meeting.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/ZoomNet/Models/Meeting.cs b/Source/ZoomNet/Models/Meeting.cs index 0fd91e2c..126c0548 100644 --- a/Source/ZoomNet/Models/Meeting.cs +++ b/Source/ZoomNet/Models/Meeting.cs @@ -24,6 +24,12 @@ public abstract class Meeting /// The id. /// [JsonPropertyName("id")] + + // This allows us to overcome the fact that "id" is sometimes a string and sometimes a number + // See: https://devforum.zoom.us/t/the-data-type-of-meetingid-is-inconsistent-in-webhook-documentation/70090 + // Also, see: https://github.com/Jericho/ZoomNet/issues/228 + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public long Id { get; set; } /// From 7e4a76e51b863312321de6882e238f8977d3bb5f Mon Sep 17 00:00:00 2001 From: Jericho Date: Fri, 15 Jul 2022 10:56:03 -0400 Subject: [PATCH 2/4] (GH-228) Unit test to demonstrate that we can deserialize meeting id when it's a string as well as an integer --- .../ZoomNet.UnitTests/WebhookParserTests.cs | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/Source/ZoomNet.UnitTests/WebhookParserTests.cs b/Source/ZoomNet.UnitTests/WebhookParserTests.cs index 83bc2023..db131a0d 100644 --- a/Source/ZoomNet.UnitTests/WebhookParserTests.cs +++ b/Source/ZoomNet.UnitTests/WebhookParserTests.cs @@ -23,7 +23,7 @@ public class WebhookParserTests ""host_id"": ""8lzIwvZTSOqjndWPbPqzuA"", ""topic"": ""ZoomNet Unit Testing: instant meeting"", ""type"": 1, - ""duration"": 60, + ""duration"": 60, ""timezone"": ""America/New_York"", ""join_url"": ""https://zoom.us/j/98884753832?pwd=c21EQzg0SXY2dlNTOFF2TENpSm1aQT09"", ""password"": ""PaSsWoRd"", @@ -51,11 +51,34 @@ public class WebhookParserTests ""type"": 1, ""duration"": 60, ""timezone"": ""America/New_York"" - } + } }, ""event_ts"": 1617628462764 }"; + private const string MEETING_ENDED_WEBHOOK = @" + { + ""event"": ""meeting.ended"", + ""event_ts"": 1626230691572, + ""payload"": { + ""account_id"": ""AAAAAABBBB"", + ""operator"": ""admin@example.com"", + ""operator_id"": ""z8yCxjabcdEFGHfp8uQ"", + ""operation"": ""single"", + ""object"": { + ""id"": ""1234567890"", + ""uuid"": ""4444AAAiAAAAAiAiAiiAii=="", + ""host_id"": ""x1yCzABCDEfg23HiJKl4mN"", + ""topic"": ""My Meeting"", + ""type"": 3, + ""start_time"": ""2021-07-13T21:44:51Z"", + ""timezone"": ""America/Los_Angeles"", + ""duration"": 60, + ""end_time"": ""2021-07-13T23:00:51Z"" + } + } + }"; + private const string MEETING_UPDATED_WEBHOOK = @" { ""event"": ""meeting.updated"", @@ -67,8 +90,8 @@ public class WebhookParserTests ""id"": 94890226305, ""topic"": ""ZoomNet Unit Testing: UPDATED scheduled meeting"", ""settings"": { ""audio"": ""voip"" } - }, - ""old_object"": { + }, + ""old_object"": { ""id"": 94890226305, ""topic"": ""ZoomNet Unit Testing: scheduled meeting"", ""settings"": { ""audio"": ""telephony"" } @@ -122,11 +145,11 @@ public class WebhookParserTests ""date_time"": ""2019-07-16T17:19:11Z"", ""content"": ""application"" } - } - }, + } + }, ""account_id"": ""EPeQtiABC000VYxHMA"" } - }"; + }"; private const string MEETING_SERVICE_ISSUE_WEBHOOK = @" { @@ -193,6 +216,24 @@ public void MeetingDeleted() parsedEvent.Meeting.Settings.ShouldBeNull(); } + [Fact] + public void MeetingEnded() + { + var parsedEvent = (MeetingEndedEvent)new WebhookParser().ParseEventWebhook(MEETING_ENDED_WEBHOOK); + + parsedEvent.EventType.ShouldBe(EventType.MeetingEnded); + parsedEvent.Timestamp.ShouldBe(1626230691572.FromUnixTime(Internal.UnixTimePrecision.Milliseconds)); + parsedEvent.Meeting.ShouldNotBeNull(); + parsedEvent.Meeting.Uuid.ShouldBe("4444AAAiAAAAAiAiAiiAii=="); + parsedEvent.Meeting.Id.ShouldBe(1234567890); + parsedEvent.Meeting.HostId.ShouldBe("x1yCzABCDEfg23HiJKl4mN"); + parsedEvent.Meeting.Topic.ShouldBe("My Meeting"); + parsedEvent.Meeting.Type.ShouldBe(MeetingType.RecurringNoFixedTime); + parsedEvent.Meeting.JoinUrl.ShouldBeNull(); + parsedEvent.Meeting.Password.ShouldBeNull(); + parsedEvent.Meeting.Settings.ShouldBeNull(); + } + [Fact] public void MeetingUpdated() { From fa80abdea8a5c51d1543cdf483def81807ff8eeb Mon Sep 17 00:00:00 2001 From: Jericho Date: Sat, 16 Jul 2022 10:37:23 -0400 Subject: [PATCH 3/4] Add overload GetPropertyValue that allows specifying multiple property names. We will search the JSON document until we find a property with one of the specified names. --- Source/ZoomNet/Extensions/Internal.cs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Source/ZoomNet/Extensions/Internal.cs b/Source/ZoomNet/Extensions/Internal.cs index 3fcdb4bb..9e5d68de 100644 --- a/Source/ZoomNet/Extensions/Internal.cs +++ b/Source/ZoomNet/Extensions/Internal.cs @@ -468,12 +468,22 @@ internal static string EnsureEndsWith(this string value, string suffix) internal static T GetPropertyValue(this JsonElement element, string name, T defaultValue) { - return GetPropertyValue(element, name, defaultValue, false); + return GetPropertyValue(element, new[] { name }, defaultValue, false); + } + + internal static T GetPropertyValue(this JsonElement element, string[] names, T defaultValue) + { + return GetPropertyValue(element, names, defaultValue, false); } internal static T GetPropertyValue(this JsonElement element, string name) { - return GetPropertyValue(element, name, default, true); + return GetPropertyValue(element, new[] { name }, default, true); + } + + internal static T GetPropertyValue(this JsonElement element, string[] names) + { + return GetPropertyValue(element, names, default, true); } internal static async Task ForEachAsync(this IEnumerable items, Func> action, int maxDegreeOfParalellism) @@ -1002,9 +1012,16 @@ private static async Task> AsPaginated return result; } - private static T GetPropertyValue(this JsonElement element, string name, T defaultValue, bool throwIfMissing) + private static T GetPropertyValue(this JsonElement element, string[] names, T defaultValue, bool throwIfMissing) { - var property = element.GetProperty(name, throwIfMissing); + JsonElement? property = null; + + foreach (var name in names) + { + property = element.GetProperty(name, false); + if (property.HasValue) break; + } + if (!property.HasValue) return defaultValue; var typeOfT = typeof(T); From 1341bbcc4c4a3867e0e00e398df9fa2a328ad850 Mon Sep 17 00:00:00 2001 From: Jericho Date: Sat, 16 Jul 2022 10:57:49 -0400 Subject: [PATCH 4/4] (GH-229) The name of the property that contains the date/time a participant joined a meeting has changed from "date_time" to "join_time". Or the documentation was wrong all along and "date_time" was never the name of the json node. --- Source/ZoomNet/Json/WebhookEventConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ZoomNet/Json/WebhookEventConverter.cs b/Source/ZoomNet/Json/WebhookEventConverter.cs index 98088a36..06c3a1c8 100644 --- a/Source/ZoomNet/Json/WebhookEventConverter.cs +++ b/Source/ZoomNet/Json/WebhookEventConverter.cs @@ -141,7 +141,7 @@ public override Event Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe break; case EventType.MeetingParticipantJoined: var meetingParticipantJoinedEvent = payloadJsonProperty.ToObject(options); - meetingParticipantJoinedEvent.JoinedOn = payloadJsonProperty.GetPropertyValue("object/participant/date_time"); + meetingParticipantJoinedEvent.JoinedOn = payloadJsonProperty.GetPropertyValue("object/participant/join_time"); meetingParticipantJoinedEvent.Participant = payloadJsonProperty.GetProperty("object/participant", true).Value.ToObject(); webHookEvent = meetingParticipantJoinedEvent; break;