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() { 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); 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; 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; } ///