From e5591eef3850a9796cc87386128651a828b70697 Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Fri, 6 Oct 2023 18:29:15 +0200 Subject: Small refactors --- .../Common/MjolnirShortcodeEventContent.cs | 11 + .../EventTypes/Common/MjolnirShortcodeEventData.cs | 11 - .../EventTypes/Common/RoomEmotesEventContent.cs | 26 +++ LibMatrix/EventTypes/Common/RoomEmotesEventData.cs | 26 --- LibMatrix/EventTypes/MatrixEventAttribute.cs | 7 + .../Spec/Ephemeral/PresenceStateEventContent.cs | 21 ++ .../Spec/Ephemeral/RoomTypingEventContent.cs | 11 + .../EventTypes/Spec/RoomMessageEventContent.cs | 33 +++ LibMatrix/EventTypes/Spec/RoomMessageEventData.cs | 33 --- .../Spec/State/CanonicalAliasEventContent.cs | 13 -- .../EventTypes/Spec/State/GuestAccessEventData.cs | 16 -- .../Spec/State/HistoryVisibilityEventData.cs | 11 - .../EventTypes/Spec/State/JoinRulesEventData.cs | 30 --- .../State/Policy/PolicyRuleStateEventContent.cs | 56 +++++ .../Spec/State/PolicyRuleStateEventData.cs | 56 ----- .../Spec/State/PresenceStateEventData.cs | 21 -- .../Spec/State/ProfileResponseEventContent.cs | 12 + .../Spec/State/ProfileResponseEventData.cs | 12 - .../EventTypes/Spec/State/RoomAliasEventData.cs | 11 - .../EventTypes/Spec/State/RoomAvatarEventData.cs | 28 --- .../EventTypes/Spec/State/RoomCreateEventData.cs | 31 --- .../Spec/State/RoomEncryptionEventData.cs | 15 -- .../Spec/State/RoomInfo/RoomAliasEventContent.cs | 11 + .../Spec/State/RoomInfo/RoomAvatarEventContent.cs | 28 +++ .../RoomInfo/RoomCanonicalAliasEventContent.cs | 13 ++ .../Spec/State/RoomInfo/RoomCreateEventContent.cs | 31 +++ .../State/RoomInfo/RoomEncryptionEventContent.cs | 15 ++ .../State/RoomInfo/RoomGuestAccessEventContent.cs | 16 ++ .../RoomInfo/RoomHistoryVisibilityEventContent.cs | 11 + .../State/RoomInfo/RoomJoinRulesEventContent.cs | 30 +++ .../Spec/State/RoomInfo/RoomMemberEventContent.cs | 29 +++ .../Spec/State/RoomInfo/RoomNameEventContent.cs | 11 + .../Spec/State/RoomInfo/RoomPinnedEventContent.cs | 11 + .../State/RoomInfo/RoomPowerLevelEventContent.cs | 56 +++++ .../State/RoomInfo/RoomServerACLEventContent.cs | 17 ++ .../Spec/State/RoomInfo/RoomTopicEventContent.cs | 12 + .../EventTypes/Spec/State/RoomMemberEventData.cs | 29 --- .../EventTypes/Spec/State/RoomNameEventData.cs | 11 - .../EventTypes/Spec/State/RoomPinnedEventData.cs | 11 - .../Spec/State/RoomPowerLevelEventData.cs | 56 ----- .../EventTypes/Spec/State/RoomTopicEventData.cs | 12 - .../EventTypes/Spec/State/RoomTypingEventData.cs | 11 - .../EventTypes/Spec/State/ServerACLEventData.cs | 17 -- .../Spec/State/Space/SpaceChildEventContent.cs | 15 ++ .../Spec/State/Space/SpaceParentEventContent.cs | 14 ++ .../EventTypes/Spec/State/SpaceChildEventData.cs | 15 -- .../EventTypes/Spec/State/SpaceParentEventData.cs | 14 -- LibMatrix/EventTypes/UnknownStateEventContent.cs | 7 + LibMatrix/EventTypes/UnknownStateEventData.cs | 7 - LibMatrix/Extensions/EnumerableExtensions.cs | 28 +++ LibMatrix/Extensions/HttpClientExtensions.cs | 14 +- LibMatrix/Helpers/MatrixEventAttribute.cs | 7 - LibMatrix/Helpers/MessageFormatter.cs | 9 +- LibMatrix/Helpers/SyncHelper.cs | 259 ++++++--------------- LibMatrix/Helpers/SyncStateResolver.cs | 174 ++++++++++++++ .../Homeservers/AuthenticatedHomeserverGeneric.cs | 42 ++-- LibMatrix/Homeservers/RemoteHomeServer.cs | 16 +- LibMatrix/Interfaces/EventContent.cs | 26 +++ LibMatrix/Interfaces/IStateEventType.cs | 23 -- LibMatrix/Responses/CreateRoomRequest.cs | 1 + LibMatrix/Responses/LoginResponse.cs | 2 +- LibMatrix/Responses/StateEventResponse.cs | 52 ----- LibMatrix/Responses/SyncResponse.cs | 118 ++++++++++ LibMatrix/RoomTypes/GenericRoom.cs | 35 ++- LibMatrix/Services/HomeserverProviderService.cs | 11 +- LibMatrix/Services/HomeserverResolverService.cs | 4 + LibMatrix/StateEvent.cs | 62 ++++- 67 files changed, 1069 insertions(+), 815 deletions(-) create mode 100644 LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs delete mode 100644 LibMatrix/EventTypes/Common/MjolnirShortcodeEventData.cs create mode 100644 LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs delete mode 100644 LibMatrix/EventTypes/Common/RoomEmotesEventData.cs create mode 100644 LibMatrix/EventTypes/MatrixEventAttribute.cs create mode 100644 LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs delete mode 100644 LibMatrix/EventTypes/Spec/RoomMessageEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/CanonicalAliasEventContent.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/GuestAccessEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/HistoryVisibilityEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/JoinRulesEventData.cs create mode 100644 LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/PolicyRuleStateEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/PresenceStateEventData.cs create mode 100644 LibMatrix/EventTypes/Spec/State/ProfileResponseEventContent.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/ProfileResponseEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomAliasEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomAvatarEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomEncryptionEventData.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomMemberEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomNameEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomPinnedEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomTopicEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/RoomTypingEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/ServerACLEventData.cs create mode 100644 LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs create mode 100644 LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs delete mode 100644 LibMatrix/EventTypes/Spec/State/SpaceParentEventData.cs create mode 100644 LibMatrix/EventTypes/UnknownStateEventContent.cs delete mode 100644 LibMatrix/EventTypes/UnknownStateEventData.cs create mode 100644 LibMatrix/Extensions/EnumerableExtensions.cs delete mode 100644 LibMatrix/Helpers/MatrixEventAttribute.cs create mode 100644 LibMatrix/Helpers/SyncStateResolver.cs create mode 100644 LibMatrix/Interfaces/EventContent.cs delete mode 100644 LibMatrix/Interfaces/IStateEventType.cs delete mode 100644 LibMatrix/Responses/StateEventResponse.cs create mode 100644 LibMatrix/Responses/SyncResponse.cs (limited to 'LibMatrix') diff --git a/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs b/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs new file mode 100644 index 0000000..9067351 --- /dev/null +++ b/LibMatrix/EventTypes/Common/MjolnirShortcodeEventContent.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Common; + +[MatrixEvent(EventName = "org.matrix.mjolnir.shortcode")] +public class MjolnirShortcodeEventContent : EventContent { + [JsonPropertyName("shortcode")] + public string? Shortcode { get; set; } +} diff --git a/LibMatrix/EventTypes/Common/MjolnirShortcodeEventData.cs b/LibMatrix/EventTypes/Common/MjolnirShortcodeEventData.cs deleted file mode 100644 index 9067351..0000000 --- a/LibMatrix/EventTypes/Common/MjolnirShortcodeEventData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Common; - -[MatrixEvent(EventName = "org.matrix.mjolnir.shortcode")] -public class MjolnirShortcodeEventContent : EventContent { - [JsonPropertyName("shortcode")] - public string? Shortcode { get; set; } -} diff --git a/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs b/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs new file mode 100644 index 0000000..abf936c --- /dev/null +++ b/LibMatrix/EventTypes/Common/RoomEmotesEventContent.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Common; + +[MatrixEvent(EventName = "im.ponies.room_emotes")] +public class RoomEmotesEventContent : EventContent { + [JsonPropertyName("emoticons")] + public Dictionary? Emoticons { get; set; } + + [JsonPropertyName("images")] + public Dictionary? Images { get; set; } + + [JsonPropertyName("pack")] + public PackInfo? Pack { get; set; } + + public class EmoticonData { + [JsonPropertyName("url")] + public string? Url { get; set; } + } + + public class PackInfo { + + } +} diff --git a/LibMatrix/EventTypes/Common/RoomEmotesEventData.cs b/LibMatrix/EventTypes/Common/RoomEmotesEventData.cs deleted file mode 100644 index abf936c..0000000 --- a/LibMatrix/EventTypes/Common/RoomEmotesEventData.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Common; - -[MatrixEvent(EventName = "im.ponies.room_emotes")] -public class RoomEmotesEventContent : EventContent { - [JsonPropertyName("emoticons")] - public Dictionary? Emoticons { get; set; } - - [JsonPropertyName("images")] - public Dictionary? Images { get; set; } - - [JsonPropertyName("pack")] - public PackInfo? Pack { get; set; } - - public class EmoticonData { - [JsonPropertyName("url")] - public string? Url { get; set; } - } - - public class PackInfo { - - } -} diff --git a/LibMatrix/EventTypes/MatrixEventAttribute.cs b/LibMatrix/EventTypes/MatrixEventAttribute.cs new file mode 100644 index 0000000..92334d0 --- /dev/null +++ b/LibMatrix/EventTypes/MatrixEventAttribute.cs @@ -0,0 +1,7 @@ +namespace LibMatrix.EventTypes; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class MatrixEventAttribute : Attribute { + public string EventName { get; set; } + public bool Legacy { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs b/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs new file mode 100644 index 0000000..b12da5b --- /dev/null +++ b/LibMatrix/EventTypes/Spec/Ephemeral/PresenceStateEventContent.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.presence")] +public class PresenceEventContent : EventContent { + [JsonPropertyName("presence")] + public string Presence { get; set; } + [JsonPropertyName("last_active_ago")] + public long LastActiveAgo { get; set; } + [JsonPropertyName("currently_active")] + public bool CurrentlyActive { get; set; } + [JsonPropertyName("status_msg")] + public string StatusMessage { get; set; } + [JsonPropertyName("avatar_url")] + public string AvatarUrl { get; set; } + [JsonPropertyName("displayname")] + public string DisplayName { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs b/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs new file mode 100644 index 0000000..01cfacf --- /dev/null +++ b/LibMatrix/EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.typing")] +public class RoomTypingEventContent : EventContent { + [JsonPropertyName("user_ids")] + public string[]? UserIds { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs new file mode 100644 index 0000000..f8ee58b --- /dev/null +++ b/LibMatrix/EventTypes/Spec/RoomMessageEventContent.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec; + +[MatrixEvent(EventName = "m.room.message")] +public class RoomMessageEventContent : EventContent { + public RoomMessageEventContent(string? messageType = "m.notice", string? body = null) { + MessageType = messageType; + Body = body; + } + + [JsonPropertyName("body")] + public string Body { get; set; } + + [JsonPropertyName("msgtype")] + public string MessageType { get; set; } = "m.notice"; + + [JsonPropertyName("formatted_body")] + public string FormattedBody { get; set; } + + [JsonPropertyName("format")] + public string Format { get; set; } + + /// + /// Media URI for this message, if any + /// + [JsonPropertyName("url")] + public string? Url { get; set; } + + public string? FileName { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs b/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs deleted file mode 100644 index f8ee58b..0000000 --- a/LibMatrix/EventTypes/Spec/RoomMessageEventData.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec; - -[MatrixEvent(EventName = "m.room.message")] -public class RoomMessageEventContent : EventContent { - public RoomMessageEventContent(string? messageType = "m.notice", string? body = null) { - MessageType = messageType; - Body = body; - } - - [JsonPropertyName("body")] - public string Body { get; set; } - - [JsonPropertyName("msgtype")] - public string MessageType { get; set; } = "m.notice"; - - [JsonPropertyName("formatted_body")] - public string FormattedBody { get; set; } - - [JsonPropertyName("format")] - public string Format { get; set; } - - /// - /// Media URI for this message, if any - /// - [JsonPropertyName("url")] - public string? Url { get; set; } - - public string? FileName { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/CanonicalAliasEventContent.cs b/LibMatrix/EventTypes/Spec/State/CanonicalAliasEventContent.cs deleted file mode 100644 index 71f3d0d..0000000 --- a/LibMatrix/EventTypes/Spec/State/CanonicalAliasEventContent.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.canonical_alias")] -public class CanonicalAliasEventContent : EventContent { - [JsonPropertyName("alias")] - public string? Alias { get; set; } - [JsonPropertyName("alt_aliases")] - public string[]? AltAliases { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/GuestAccessEventData.cs b/LibMatrix/EventTypes/Spec/State/GuestAccessEventData.cs deleted file mode 100644 index af1b2ce..0000000 --- a/LibMatrix/EventTypes/Spec/State/GuestAccessEventData.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.guest_access")] -public class GuestAccessEventContent : EventContent { - [JsonPropertyName("guest_access")] - public string GuestAccess { get; set; } - - public bool IsGuestAccessEnabled { - get => GuestAccess == "can_join"; - set => GuestAccess = value ? "can_join" : "forbidden"; - } -} diff --git a/LibMatrix/EventTypes/Spec/State/HistoryVisibilityEventData.cs b/LibMatrix/EventTypes/Spec/State/HistoryVisibilityEventData.cs deleted file mode 100644 index b57ade5..0000000 --- a/LibMatrix/EventTypes/Spec/State/HistoryVisibilityEventData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.history_visibility")] -public class HistoryVisibilityEventContent : EventContent { - [JsonPropertyName("history_visibility")] - public string HistoryVisibility { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/JoinRulesEventData.cs b/LibMatrix/EventTypes/Spec/State/JoinRulesEventData.cs deleted file mode 100644 index 0098bef..0000000 --- a/LibMatrix/EventTypes/Spec/State/JoinRulesEventData.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.join_rules")] -public class JoinRulesEventContent : EventContent { - private static string Public = "public"; - private static string Invite = "invite"; - private static string Knock = "knock"; - - /// - /// one of ["public", "invite", "knock", "restricted", "knock_restricted"] - /// "private" is reserved without implementation! - /// - [JsonPropertyName("join_rule")] - public string JoinRule { get; set; } - - [JsonPropertyName("allow")] - public List Allow { get; set; } - - public class AllowEntry { - [JsonPropertyName("type")] - public string Type { get; set; } - - [JsonPropertyName("room_id")] - public string RoomId { get; set; } - } -} diff --git a/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs new file mode 100644 index 0000000..fde02c1 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs @@ -0,0 +1,56 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.policy.rule.user")] +[MatrixEvent(EventName = "m.policy.rule.server")] +[MatrixEvent(EventName = "org.matrix.mjolnir.rule.server")] +public class PolicyRuleEventContent : EventContent { + /// + /// Entity this ban applies to, can use * and ? as globs. + /// + [JsonPropertyName("entity")] + public string Entity { get; set; } + + /// + /// Reason this user is banned + /// + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// + /// Suggested action to take + /// + [JsonPropertyName("recommendation")] + public string? Recommendation { get; set; } + + /// + /// Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry. + /// + [JsonPropertyName("support.feline.policy.expiry.rev.2")] //stable prefix: expiry, msc pending + public long? Expiry { get; set; } + + //utils + /// + /// Readable expiry time, provided for easy interaction + /// + [JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")] + public DateTime? ExpiryDateTime { + get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime; + set => Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds(); + } +} + +public static class PolicyRecommendationTypes { + /// + /// Ban this user + /// + public static string Ban = "m.ban"; + + /// + /// Mute this user + /// + public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending +} diff --git a/LibMatrix/EventTypes/Spec/State/PolicyRuleStateEventData.cs b/LibMatrix/EventTypes/Spec/State/PolicyRuleStateEventData.cs deleted file mode 100644 index fde02c1..0000000 --- a/LibMatrix/EventTypes/Spec/State/PolicyRuleStateEventData.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.policy.rule.user")] -[MatrixEvent(EventName = "m.policy.rule.server")] -[MatrixEvent(EventName = "org.matrix.mjolnir.rule.server")] -public class PolicyRuleEventContent : EventContent { - /// - /// Entity this ban applies to, can use * and ? as globs. - /// - [JsonPropertyName("entity")] - public string Entity { get; set; } - - /// - /// Reason this user is banned - /// - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - /// - /// Suggested action to take - /// - [JsonPropertyName("recommendation")] - public string? Recommendation { get; set; } - - /// - /// Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry. - /// - [JsonPropertyName("support.feline.policy.expiry.rev.2")] //stable prefix: expiry, msc pending - public long? Expiry { get; set; } - - //utils - /// - /// Readable expiry time, provided for easy interaction - /// - [JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")] - public DateTime? ExpiryDateTime { - get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime; - set => Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds(); - } -} - -public static class PolicyRecommendationTypes { - /// - /// Ban this user - /// - public static string Ban = "m.ban"; - - /// - /// Mute this user - /// - public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending -} diff --git a/LibMatrix/EventTypes/Spec/State/PresenceStateEventData.cs b/LibMatrix/EventTypes/Spec/State/PresenceStateEventData.cs deleted file mode 100644 index b12da5b..0000000 --- a/LibMatrix/EventTypes/Spec/State/PresenceStateEventData.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.presence")] -public class PresenceEventContent : EventContent { - [JsonPropertyName("presence")] - public string Presence { get; set; } - [JsonPropertyName("last_active_ago")] - public long LastActiveAgo { get; set; } - [JsonPropertyName("currently_active")] - public bool CurrentlyActive { get; set; } - [JsonPropertyName("status_msg")] - public string StatusMessage { get; set; } - [JsonPropertyName("avatar_url")] - public string AvatarUrl { get; set; } - [JsonPropertyName("displayname")] - public string DisplayName { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/ProfileResponseEventContent.cs b/LibMatrix/EventTypes/Spec/State/ProfileResponseEventContent.cs new file mode 100644 index 0000000..893fce1 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/ProfileResponseEventContent.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +public class ProfileResponseEventContent : EventContent { + [JsonPropertyName("avatar_url")] + public string? AvatarUrl { get; set; } + + [JsonPropertyName("displayname")] + public string? DisplayName { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/ProfileResponseEventData.cs b/LibMatrix/EventTypes/Spec/State/ProfileResponseEventData.cs deleted file mode 100644 index 893fce1..0000000 --- a/LibMatrix/EventTypes/Spec/State/ProfileResponseEventData.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -public class ProfileResponseEventContent : EventContent { - [JsonPropertyName("avatar_url")] - public string? AvatarUrl { get; set; } - - [JsonPropertyName("displayname")] - public string? DisplayName { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomAliasEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomAliasEventData.cs deleted file mode 100644 index 5b0e914..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomAliasEventData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.alias")] -public class RoomAliasEventContent : EventContent { - [JsonPropertyName("aliases")] - public List? Aliases { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomAvatarEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomAvatarEventData.cs deleted file mode 100644 index 601d014..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomAvatarEventData.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.avatar")] -public class RoomAvatarEventContent : EventContent { - [JsonPropertyName("url")] - public string? Url { get; set; } - - [JsonPropertyName("info")] - public RoomAvatarInfo? Info { get; set; } - - public class RoomAvatarInfo { - [JsonPropertyName("h")] - public int? Height { get; set; } - - [JsonPropertyName("w")] - public int? Width { get; set; } - - [JsonPropertyName("mimetype")] - public string? MimeType { get; set; } - - [JsonPropertyName("size")] - public int? Size { get; set; } - } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs deleted file mode 100644 index c5bf14e..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomCreateEventData.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.create")] -public class RoomCreateEventContent : EventContent { - [JsonPropertyName("room_version")] - public string? RoomVersion { get; set; } - - [JsonPropertyName("creator")] - public string? Creator { get; set; } - - [JsonPropertyName("m.federate")] - public bool? Federate { get; set; } - - [JsonPropertyName("predecessor")] - public RoomCreatePredecessor? Predecessor { get; set; } - - [JsonPropertyName("type")] - public string? Type { get; set; } - - public class RoomCreatePredecessor { - [JsonPropertyName("room_id")] - public string? RoomId { get; set; } - - [JsonPropertyName("event_id")] - public string? EventId { get; set; } - } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomEncryptionEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomEncryptionEventData.cs deleted file mode 100644 index 6ffa4c5..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomEncryptionEventData.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.encryption")] -public class RoomEncryptionEventContent : EventContent { - [JsonPropertyName("algorithm")] - public string? Algorithm { get; set; } - [JsonPropertyName("rotation_period_ms")] - public ulong? RotationPeriodMs { get; set; } - [JsonPropertyName("rotation_period_msgs")] - public ulong? RotationPeriodMsgs { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs new file mode 100644 index 0000000..5b0e914 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAliasEventContent.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.alias")] +public class RoomAliasEventContent : EventContent { + [JsonPropertyName("aliases")] + public List? Aliases { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs new file mode 100644 index 0000000..601d014 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomAvatarEventContent.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.avatar")] +public class RoomAvatarEventContent : EventContent { + [JsonPropertyName("url")] + public string? Url { get; set; } + + [JsonPropertyName("info")] + public RoomAvatarInfo? Info { get; set; } + + public class RoomAvatarInfo { + [JsonPropertyName("h")] + public int? Height { get; set; } + + [JsonPropertyName("w")] + public int? Width { get; set; } + + [JsonPropertyName("mimetype")] + public string? MimeType { get; set; } + + [JsonPropertyName("size")] + public int? Size { get; set; } + } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs new file mode 100644 index 0000000..046222e --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.canonical_alias")] +public class RoomCanonicalAliasEventContent : EventContent { + [JsonPropertyName("alias")] + public string? Alias { get; set; } + [JsonPropertyName("alt_aliases")] + public string[]? AltAliases { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs new file mode 100644 index 0000000..c5bf14e --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.create")] +public class RoomCreateEventContent : EventContent { + [JsonPropertyName("room_version")] + public string? RoomVersion { get; set; } + + [JsonPropertyName("creator")] + public string? Creator { get; set; } + + [JsonPropertyName("m.federate")] + public bool? Federate { get; set; } + + [JsonPropertyName("predecessor")] + public RoomCreatePredecessor? Predecessor { get; set; } + + [JsonPropertyName("type")] + public string? Type { get; set; } + + public class RoomCreatePredecessor { + [JsonPropertyName("room_id")] + public string? RoomId { get; set; } + + [JsonPropertyName("event_id")] + public string? EventId { get; set; } + } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs new file mode 100644 index 0000000..6ffa4c5 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.encryption")] +public class RoomEncryptionEventContent : EventContent { + [JsonPropertyName("algorithm")] + public string? Algorithm { get; set; } + [JsonPropertyName("rotation_period_ms")] + public ulong? RotationPeriodMs { get; set; } + [JsonPropertyName("rotation_period_msgs")] + public ulong? RotationPeriodMsgs { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs new file mode 100644 index 0000000..2bb4d36 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.guest_access")] +public class RoomGuestAccessEventContent : EventContent { + [JsonPropertyName("guest_access")] + public string GuestAccess { get; set; } + + public bool IsGuestAccessEnabled { + get => GuestAccess == "can_join"; + set => GuestAccess = value ? "can_join" : "forbidden"; + } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs new file mode 100644 index 0000000..a32fed2 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.history_visibility")] +public class RoomHistoryVisibilityEventContent : EventContent { + [JsonPropertyName("history_visibility")] + public string HistoryVisibility { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs new file mode 100644 index 0000000..2c2a91b --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs @@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.join_rules")] +public class RoomJoinRulesEventContent : EventContent { + private static string Public = "public"; + private static string Invite = "invite"; + private static string Knock = "knock"; + + /// + /// one of ["public", "invite", "knock", "restricted", "knock_restricted"] + /// "private" is reserved without implementation! + /// + [JsonPropertyName("join_rule")] + public string JoinRule { get; set; } + + [JsonPropertyName("allow")] + public List Allow { get; set; } + + public class AllowEntry { + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("room_id")] + public string RoomId { get; set; } + } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs new file mode 100644 index 0000000..52cb293 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs @@ -0,0 +1,29 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.member")] +public class RoomMemberEventContent : EventContent { + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + [JsonPropertyName("membership")] + public string Membership { get; set; } = null!; + + [JsonPropertyName("displayname")] + public string? DisplayName { get; set; } + + [JsonPropertyName("is_direct")] + public bool? IsDirect { get; set; } + + [JsonPropertyName("avatar_url")] + public string? AvatarUrl { get; set; } + + [JsonPropertyName("kind")] + public string? Kind { get; set; } + + [JsonPropertyName("join_authorised_via_users_server")] + public string? JoinAuthorisedViaUsersServer { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs new file mode 100644 index 0000000..7cb881a --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.name")] +public class RoomNameEventContent : EventContent { + [JsonPropertyName("name")] + public string? Name { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs new file mode 100644 index 0000000..eb02cc7 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.pinned_events")] +public class RoomPinnedEventContent : EventContent { + [JsonPropertyName("pinned")] + public string[]? PinnedEvents { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs new file mode 100644 index 0000000..2ae9593 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs @@ -0,0 +1,56 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.power_levels")] +public class RoomPowerLevelEventContent : EventContent { + [JsonPropertyName("ban")] + public long? Ban { get; set; } = 50; + + [JsonPropertyName("events_default")] + public long EventsDefault { get; set; } = 0; + + [JsonPropertyName("events")] + public Dictionary? Events { get; set; } // = null!; + + [JsonPropertyName("invite")] + public long? Invite { get; set; } = 0; + + [JsonPropertyName("kick")] + public long? Kick { get; set; } = 50; + + [JsonPropertyName("notifications")] + public NotificationsPL? NotificationsPl { get; set; } // = null!; + + [JsonPropertyName("redact")] + public long? Redact { get; set; } = 50; + + [JsonPropertyName("state_default")] + public long? StateDefault { get; set; } = 50; + + [JsonPropertyName("users")] + public Dictionary? Users { get; set; } // = null!; + + [JsonPropertyName("users_default")] + public long? UsersDefault { get; set; } = 0; + + [Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)] + [JsonIgnore] + [JsonPropertyName("historical")] + public long Historical { get; set; } // = 50; + + public class NotificationsPL { + [JsonPropertyName("room")] + public long Room { get; set; } = 50; + } + + public bool IsUserAdmin(string userId) { + return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value); + } + + public bool UserHasPermission(string userId, string eventType) { + return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault); + } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs new file mode 100644 index 0000000..5c5627c --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.server_acl")] +public class RoomServerACLEventContent : EventContent { + [JsonPropertyName("allow")] + public List Allow { get; set; } // = null!; + + [JsonPropertyName("deny")] + public List Deny { get; set; } // = null!; + + [JsonPropertyName("allow_ip_literals")] + public bool AllowIpLiterals { get; set; } // = false; +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs new file mode 100644 index 0000000..52c7e42 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.room.topic")] +[MatrixEvent(EventName = "org.matrix.msc3765.topic", Legacy = true)] +public class RoomTopicEventContent : EventContent { + [JsonPropertyName("topic")] + public string? Topic { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/RoomMemberEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomMemberEventData.cs deleted file mode 100644 index da158f1..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomMemberEventData.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.member")] -public class RoomMemberEventContent : EventContent { - [JsonPropertyName("reason")] - public string? Reason { get; set; } - - [JsonPropertyName("membership")] - public string Membership { get; set; } = null!; - - [JsonPropertyName("displayname")] - public string? Displayname { get; set; } - - [JsonPropertyName("is_direct")] - public bool? IsDirect { get; set; } - - [JsonPropertyName("avatar_url")] - public string? AvatarUrl { get; set; } - - [JsonPropertyName("kind")] - public string? Kind { get; set; } - - [JsonPropertyName("join_authorised_via_users_server")] - public string? JoinAuthorisedViaUsersServer { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomNameEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomNameEventData.cs deleted file mode 100644 index 7cb881a..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomNameEventData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.name")] -public class RoomNameEventContent : EventContent { - [JsonPropertyName("name")] - public string? Name { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomPinnedEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomPinnedEventData.cs deleted file mode 100644 index eb02cc7..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomPinnedEventData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.pinned_events")] -public class RoomPinnedEventContent : EventContent { - [JsonPropertyName("pinned")] - public string[]? PinnedEvents { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs deleted file mode 100644 index 2ae9593..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomPowerLevelEventData.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.power_levels")] -public class RoomPowerLevelEventContent : EventContent { - [JsonPropertyName("ban")] - public long? Ban { get; set; } = 50; - - [JsonPropertyName("events_default")] - public long EventsDefault { get; set; } = 0; - - [JsonPropertyName("events")] - public Dictionary? Events { get; set; } // = null!; - - [JsonPropertyName("invite")] - public long? Invite { get; set; } = 0; - - [JsonPropertyName("kick")] - public long? Kick { get; set; } = 50; - - [JsonPropertyName("notifications")] - public NotificationsPL? NotificationsPl { get; set; } // = null!; - - [JsonPropertyName("redact")] - public long? Redact { get; set; } = 50; - - [JsonPropertyName("state_default")] - public long? StateDefault { get; set; } = 50; - - [JsonPropertyName("users")] - public Dictionary? Users { get; set; } // = null!; - - [JsonPropertyName("users_default")] - public long? UsersDefault { get; set; } = 0; - - [Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)] - [JsonIgnore] - [JsonPropertyName("historical")] - public long Historical { get; set; } // = 50; - - public class NotificationsPL { - [JsonPropertyName("room")] - public long Room { get; set; } = 50; - } - - public bool IsUserAdmin(string userId) { - return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value); - } - - public bool UserHasPermission(string userId, string eventType) { - return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault); - } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomTopicEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomTopicEventData.cs deleted file mode 100644 index 52c7e42..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomTopicEventData.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.topic")] -[MatrixEvent(EventName = "org.matrix.msc3765.topic", Legacy = true)] -public class RoomTopicEventContent : EventContent { - [JsonPropertyName("topic")] - public string? Topic { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/RoomTypingEventData.cs b/LibMatrix/EventTypes/Spec/State/RoomTypingEventData.cs deleted file mode 100644 index 01cfacf..0000000 --- a/LibMatrix/EventTypes/Spec/State/RoomTypingEventData.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.typing")] -public class RoomTypingEventContent : EventContent { - [JsonPropertyName("user_ids")] - public string[]? UserIds { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/ServerACLEventData.cs b/LibMatrix/EventTypes/Spec/State/ServerACLEventData.cs deleted file mode 100644 index f18fe43..0000000 --- a/LibMatrix/EventTypes/Spec/State/ServerACLEventData.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.room.server_acl")] -public class ServerACLEventContent : EventContent { - [JsonPropertyName("allow")] - public List Allow { get; set; } // = null!; - - [JsonPropertyName("deny")] - public List Deny { get; set; } // = null!; - - [JsonPropertyName("allow_ip_literals")] - public bool AllowIpLiterals { get; set; } // = false; -} diff --git a/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs b/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs new file mode 100644 index 0000000..0a897dc --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/Space/SpaceChildEventContent.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.space.child")] +public class SpaceChildEventContent : EventContent { + [JsonPropertyName("auto_join")] + public bool? AutoJoin { get; set; } + [JsonPropertyName("via")] + public List? Via { get; set; } + [JsonPropertyName("suggested")] + public bool? Suggested { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs b/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs new file mode 100644 index 0000000..0ffa193 --- /dev/null +++ b/LibMatrix/EventTypes/Spec/State/Space/SpaceParentEventContent.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes.Spec.State; + +[MatrixEvent(EventName = "m.space.parent")] +public class SpaceParentEventContent : EventContent { + [JsonPropertyName("via")] + public string[]? Via { get; set; } + + [JsonPropertyName("canonical")] + public bool? Canonical { get; set; } +} diff --git a/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs b/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs deleted file mode 100644 index 0a897dc..0000000 --- a/LibMatrix/EventTypes/Spec/State/SpaceChildEventData.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.space.child")] -public class SpaceChildEventContent : EventContent { - [JsonPropertyName("auto_join")] - public bool? AutoJoin { get; set; } - [JsonPropertyName("via")] - public List? Via { get; set; } - [JsonPropertyName("suggested")] - public bool? Suggested { get; set; } -} diff --git a/LibMatrix/EventTypes/Spec/State/SpaceParentEventData.cs b/LibMatrix/EventTypes/Spec/State/SpaceParentEventData.cs deleted file mode 100644 index 0ffa193..0000000 --- a/LibMatrix/EventTypes/Spec/State/SpaceParentEventData.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; -using LibMatrix.Helpers; -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes.Spec.State; - -[MatrixEvent(EventName = "m.space.parent")] -public class SpaceParentEventContent : EventContent { - [JsonPropertyName("via")] - public string[]? Via { get; set; } - - [JsonPropertyName("canonical")] - public bool? Canonical { get; set; } -} diff --git a/LibMatrix/EventTypes/UnknownStateEventContent.cs b/LibMatrix/EventTypes/UnknownStateEventContent.cs new file mode 100644 index 0000000..9a276c8 --- /dev/null +++ b/LibMatrix/EventTypes/UnknownStateEventContent.cs @@ -0,0 +1,7 @@ +using LibMatrix.Interfaces; + +namespace LibMatrix.EventTypes; + +public class UnknownEventContent : EventContent { + +} diff --git a/LibMatrix/EventTypes/UnknownStateEventData.cs b/LibMatrix/EventTypes/UnknownStateEventData.cs deleted file mode 100644 index 9a276c8..0000000 --- a/LibMatrix/EventTypes/UnknownStateEventData.cs +++ /dev/null @@ -1,7 +0,0 @@ -using LibMatrix.Interfaces; - -namespace LibMatrix.EventTypes; - -public class UnknownEventContent : EventContent { - -} diff --git a/LibMatrix/Extensions/EnumerableExtensions.cs b/LibMatrix/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..d9619b7 --- /dev/null +++ b/LibMatrix/Extensions/EnumerableExtensions.cs @@ -0,0 +1,28 @@ +namespace LibMatrix.Extensions; + +public static class EnumerableExtensions { + public static void MergeStateEventLists(this List oldState, List newState) { + foreach (var stateEvent in newState) { + var old = oldState.FirstOrDefault(x => x.Type == stateEvent.Type && x.StateKey == stateEvent.StateKey); + if (old is null) { + oldState.Add(stateEvent); + continue; + } + oldState.Remove(old); + oldState.Add(stateEvent); + } + } + + public static void MergeStateEventLists(this List oldState, List newState) { + foreach (var stateEvent in newState) { + var old = oldState.FirstOrDefault(x => x.Type == stateEvent.Type && x.StateKey == stateEvent.StateKey); + if (old is null) { + oldState.Add(stateEvent); + continue; + } + oldState.Remove(old); + oldState.Add(stateEvent); + } + } + +} diff --git a/LibMatrix/Extensions/HttpClientExtensions.cs b/LibMatrix/Extensions/HttpClientExtensions.cs index a5eb40f..2fe99b6 100644 --- a/LibMatrix/Extensions/HttpClientExtensions.cs +++ b/LibMatrix/Extensions/HttpClientExtensions.cs @@ -68,7 +68,16 @@ public class MatrixHttpClient : HttpClient { var response = await SendAsync(request, cancellationToken); response.EnsureSuccessStatusCode(); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); - return await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken); +#if DEBUG && false // This is only used for testing, so it's disabled by default + try { + await PostAsync("http://localhost:5116/validate/" + typeof(T).AssemblyQualifiedName, new StreamContent(responseStream), cancellationToken); + } + catch (Exception e) { + Console.WriteLine("[!!] Checking sync response failed: " + e); + } +#endif + return await JsonSerializer.DeserializeAsync(responseStream, cancellationToken: cancellationToken) ?? + throw new InvalidOperationException("Failed to deserialize response"); } // GetStreamAsync @@ -80,7 +89,8 @@ public class MatrixHttpClient : HttpClient { return await response.Content.ReadAsStreamAsync(cancellationToken); } - public new async Task PutAsJsonAsync([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null, CancellationToken cancellationToken = default) { + public new async Task PutAsJsonAsync([StringSyntax(StringSyntaxAttribute.Uri)] string? requestUri, T value, JsonSerializerOptions? options = null, + CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Put, requestUri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); request.Content = new StringContent(JsonSerializer.Serialize(value, value.GetType()), Encoding.UTF8, "application/json"); diff --git a/LibMatrix/Helpers/MatrixEventAttribute.cs b/LibMatrix/Helpers/MatrixEventAttribute.cs deleted file mode 100644 index 7efc039..0000000 --- a/LibMatrix/Helpers/MatrixEventAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LibMatrix.Helpers; - -[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] -public class MatrixEventAttribute : Attribute { - public string EventName { get; set; } - public bool Legacy { get; set; } -} diff --git a/LibMatrix/Helpers/MessageFormatter.cs b/LibMatrix/Helpers/MessageFormatter.cs index ae02afc..d252e85 100644 --- a/LibMatrix/Helpers/MessageFormatter.cs +++ b/LibMatrix/Helpers/MessageFormatter.cs @@ -13,8 +13,7 @@ public static class MessageFormatter { public static RoomMessageEventContent FormatException(string error, Exception e) { return new RoomMessageEventContent(body: $"{error}: {e.Message}", messageType: "m.text") { - FormattedBody = $"{error}:
{e.Message}
" + - $"
", + FormattedBody = $"{error}:
{e.Message}
", Format = "org.matrix.custom.html" }; } @@ -36,4 +35,10 @@ public static class MessageFormatter { public static string HtmlFormatMention(string id, string? displayName = null) { return $"{displayName ?? id}"; } + +#region Extension functions + + public static RoomMessageEventContent ToMatrixMessage(this Exception e, string error) => FormatException(error, e); + +#endregion } diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index 74972a1..06ae3fe 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -1,228 +1,115 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.Http.Json; -using System.Text.Json.Serialization; using ArcaneLibs.Extensions; using LibMatrix.Filters; using LibMatrix.Homeservers; using LibMatrix.Responses; using LibMatrix.Services; +using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; -public class SyncHelper(AuthenticatedHomeserverGeneric homeserver) { - public async Task Sync( - string? since = null, - int? timeout = 30000, - string? setPresence = "online", - SyncFilter? filter = null, - CancellationToken? cancellationToken = null) { - var url = $"/_matrix/client/v3/sync?timeout={timeout}&set_presence={setPresence}"; - if (!string.IsNullOrWhiteSpace(since)) url += $"&since={since}"; - if (filter is not null) url += $"&filter={filter.ToJson(ignoreNull: true, indent: false)}"; - // else url += "&full_state=true"; - Console.WriteLine("Calling: " + url); - try { - var req = await homeserver._httpClient.GetAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None); +public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null) { + public string? Since { get; set; } + public int Timeout { get; set; } = 30000; + public string? SetPresence { get; set; } = "online"; + public SyncFilter? Filter { get; set; } + public bool FullState { get; set; } = false; -#if DEBUG && false - try { - await homeserver._httpClient.PostAsync( - "http://localhost:5116/validate/" + typeof(SyncResult).AssemblyQualifiedName, - new StreamContent(await req.Content.ReadAsStreamAsync())); - } - catch (Exception e) { - Console.WriteLine("[!!] Checking sync response failed: " + e); - } - var res = await req.Content.ReadFromJsonAsync(); - return res; -#else - return await req.Content.ReadFromJsonAsync(); -#endif + public async Task SyncAsync(CancellationToken? cancellationToken = null) { + var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}"; + if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}"; + if (Filter is not null) url += $"&filter={Filter.ToJson(ignoreNull: true, indent: false)}"; + // Console.WriteLine("Calling: " + url); + logger?.LogInformation("SyncHelper: Calling: {}", url); + try { + return await homeserver._httpClient.GetFromJsonAsync(url, cancellationToken: cancellationToken ?? CancellationToken.None); } catch (TaskCanceledException) { Console.WriteLine("Sync cancelled!"); + logger?.LogWarning("Sync cancelled due to TaskCanceledException!"); } catch (Exception e) { Console.WriteLine(e); + logger?.LogError(e, "Failed to sync!\n{}", e.ToString()); } return null; } - [SuppressMessage("ReSharper", "FunctionNeverReturns")] - public async Task RunSyncLoop( - bool skipInitialSyncEvents = true, - string? since = null, - int? timeout = 30000, - string? setPresence = "online", - SyncFilter? filter = null, - CancellationToken? cancellationToken = null - ) { - // await Task.WhenAll((await storageService.CacheStorageProvider.GetAllKeysAsync()) - // .Where(x => x.StartsWith("sync")) - // .ToList() - // .Select(x => storageService.CacheStorageProvider.DeleteObjectAsync(x))); - var nextBatch = since; - while (cancellationToken is null || !cancellationToken.Value.IsCancellationRequested) { - var sync = await Sync(since: nextBatch, timeout: timeout, setPresence: setPresence, filter: filter, - cancellationToken: cancellationToken); - nextBatch = sync?.NextBatch ?? nextBatch; + public async IAsyncEnumerable EnumerateSyncAsync(CancellationToken? cancellationToken = null) { + while(!cancellationToken?.IsCancellationRequested ?? true) { + var sync = await SyncAsync(cancellationToken); if (sync is null) continue; - Console.WriteLine($"Got sync, next batch: {nextBatch}!"); - - if (sync.Rooms is { Invite.Count: > 0 }) { - foreach (var roomInvite in sync.Rooms.Invite) { - var tasks = InviteReceivedHandlers.Select(x => x(roomInvite)).ToList(); - await Task.WhenAll(tasks); - } - } - - if (sync.AccountData is { Events: { Count: > 0 } }) { - foreach (var accountDataEvent in sync.AccountData.Events) { - var tasks = AccountDataReceivedHandlers.Select(x => x(accountDataEvent)).ToList(); - await Task.WhenAll(tasks); - } - } - - // Things that are skipped on the first sync - if (skipInitialSyncEvents) { - skipInitialSyncEvents = false; - continue; - } - - if (sync.Rooms is { Join.Count: > 0 }) { - foreach (var updatedRoom in sync.Rooms.Join) { - if(updatedRoom.Value.Timeline is null) continue; - foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events) { - stateEventResponse.RoomId = updatedRoom.Key; - var tasks = TimelineEventHandlers.Select(x => { - try { - return x(stateEventResponse); - } - catch (Exception e) { - Console.WriteLine(e); - return Task.CompletedTask; - } - }).ToList(); - await Task.WhenAll(tasks); - } - } - } + Since = sync.NextBatch ?? Since; + yield return sync; } } - /// - /// Event fired when a room invite is received - /// - public List, Task>> - InviteReceivedHandlers { get; } = new(); - - public List> TimelineEventHandlers { get; } = new(); - public List> AccountDataReceivedHandlers { get; } = new(); -} - -public class SyncResult { - [JsonPropertyName("next_batch")] - public string NextBatch { get; set; } - - [JsonPropertyName("account_data")] - public EventList? AccountData { get; set; } - - [JsonPropertyName("presence")] - public PresenceDataStructure? Presence { get; set; } - - [JsonPropertyName("device_one_time_keys_count")] - public Dictionary DeviceOneTimeKeysCount { get; set; } - - [JsonPropertyName("rooms")] - public RoomsDataStructure? Rooms { get; set; } - - [JsonPropertyName("to_device")] - public EventList? ToDevice { get; set; } - - [JsonPropertyName("device_lists")] - public DeviceListsDataStructure? DeviceLists { get; set; } - - public class DeviceListsDataStructure { - [JsonPropertyName("changed")] - public List? Changed { get; set; } - - [JsonPropertyName("left")] - public List? Left { get; set; } - } - - // supporting classes - public class PresenceDataStructure { - [JsonPropertyName("events")] - public List Events { get; set; } + public async Task RunSyncLoopAsync(bool skipInitialSyncEvents = true, CancellationToken? cancellationToken = null) { + var sw = Stopwatch.StartNew(); + await foreach (var sync in EnumerateSyncAsync(cancellationToken)) { + logger?.LogInformation("Got sync response: {} bytes, {} elapsed", sync?.ToJson(ignoreNull: true, indent: false).Length ?? -1, sw.Elapsed); + await RunSyncLoopCallbacksAsync(sync, Since is null && skipInitialSyncEvents); + } } - public class RoomsDataStructure { - [JsonPropertyName("join")] - public Dictionary? Join { get; set; } - - [JsonPropertyName("invite")] - public Dictionary? Invite { get; set; } - - public class JoinedRoomDataStructure { - [JsonPropertyName("timeline")] - public TimelineDataStructure? Timeline { get; set; } - - [JsonPropertyName("state")] - public EventList State { get; set; } - - [JsonPropertyName("account_data")] - public EventList AccountData { get; set; } - - [JsonPropertyName("ephemeral")] - public EventList Ephemeral { get; set; } - - [JsonPropertyName("unread_notifications")] - public UnreadNotificationsDataStructure UnreadNotifications { get; set; } - - [JsonPropertyName("summary")] - public SummaryDataStructure Summary { get; set; } + private async Task RunSyncLoopCallbacksAsync(SyncResponse syncResponse, bool isInitialSync) { - public class TimelineDataStructure { - [JsonPropertyName("events")] - public List Events { get; set; } + var tasks = SyncReceivedHandlers.Select(x => x(syncResponse)).ToList(); + await Task.WhenAll(tasks); - [JsonPropertyName("prev_batch")] - public string PrevBatch { get; set; } - - [JsonPropertyName("limited")] - public bool Limited { get; set; } + if (syncResponse.AccountData is { Events: { Count: > 0 } }) { + foreach (var accountDataEvent in syncResponse.AccountData.Events) { + tasks = AccountDataReceivedHandlers.Select(x => x(accountDataEvent)).ToList(); + await Task.WhenAll(tasks); } + } - public class UnreadNotificationsDataStructure { - [JsonPropertyName("notification_count")] - public int NotificationCount { get; set; } + await RunSyncLoopRoomCallbacksAsync(syncResponse, isInitialSync); + } - [JsonPropertyName("highlight_count")] - public int HighlightCount { get; set; } + private async Task RunSyncLoopRoomCallbacksAsync(SyncResponse syncResponse, bool isInitialSync) { + if (syncResponse.Rooms is { Invite.Count: > 0 }) { + foreach (var roomInvite in syncResponse.Rooms.Invite) { + var tasks = InviteReceivedHandlers.Select(x => x(roomInvite)).ToList(); + await Task.WhenAll(tasks); } + } - public class SummaryDataStructure { - [JsonPropertyName("m.heroes")] - public List Heroes { get; set; } - - [JsonPropertyName("m.invited_member_count")] - public int InvitedMemberCount { get; set; } + if (isInitialSync) return; - [JsonPropertyName("m.joined_member_count")] - public int JoinedMemberCount { get; set; } + if (syncResponse.Rooms is { Join.Count: > 0 }) { + foreach (var updatedRoom in syncResponse.Rooms.Join) { + if (updatedRoom.Value.Timeline is null) continue; + foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events) { + stateEventResponse.RoomId = updatedRoom.Key; + var tasks = TimelineEventHandlers.Select(x => x(stateEventResponse)).ToList(); + await Task.WhenAll(tasks); + } } } - - public class InvitedRoomDataStructure { - [JsonPropertyName("invite_state")] - public EventList InviteState { get; set; } - } } -} -public class EventList { - [JsonPropertyName("events")] - public List Events { get; set; } + /// + /// Event fired when a sync response is received + /// + public List> SyncReceivedHandlers { get; } = new(); + + /// + /// Event fired when a room invite is received + /// + public List, Task>> InviteReceivedHandlers { get; } = new(); + + /// + /// Event fired when a timeline event is received + /// + public List> TimelineEventHandlers { get; } = new(); + + /// + /// Event fired when an account data event is received + /// + public List> AccountDataReceivedHandlers { get; } = new(); } diff --git a/LibMatrix/Helpers/SyncStateResolver.cs b/LibMatrix/Helpers/SyncStateResolver.cs new file mode 100644 index 0000000..0070d60 --- /dev/null +++ b/LibMatrix/Helpers/SyncStateResolver.cs @@ -0,0 +1,174 @@ +using LibMatrix.Extensions; +using LibMatrix.Filters; +using LibMatrix.Homeservers; +using LibMatrix.Responses; +using Microsoft.Extensions.Logging; + +namespace LibMatrix.Helpers; + +public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null) { + public string? Since { get; set; } + public int Timeout { get; set; } = 30000; + public string? SetPresence { get; set; } = "online"; + public SyncFilter? Filter { get; set; } + public bool FullState { get; set; } = false; + + public SyncResponse? MergedState { get; set; } = null!; + + private SyncHelper _syncHelper = new SyncHelper(homeserver, logger); + + public async Task<(SyncResponse next, SyncResponse merged)> ContinueAsync(CancellationToken? cancellationToken = null) { + // copy properties + _syncHelper.Since = Since; + _syncHelper.Timeout = Timeout; + _syncHelper.SetPresence = SetPresence; + _syncHelper.Filter = Filter; + _syncHelper.FullState = FullState; + // run sync + var sync = await _syncHelper.SyncAsync(cancellationToken); + if (sync is null) return await ContinueAsync(cancellationToken); + if (MergedState is null) MergedState = sync; + else MergedState = MergeSyncs(MergedState, sync); + Since = sync.NextBatch; + return (sync, MergedState); + } + + private SyncResponse MergeSyncs(SyncResponse oldState, SyncResponse newState) { + oldState.NextBatch = newState.NextBatch ?? oldState.NextBatch; + + oldState.AccountData ??= new(); + oldState.AccountData.Events ??= new(); + if (newState.AccountData?.Events is not null) + oldState.AccountData.Events.MergeStateEventLists(newState.AccountData?.Events ?? new()); + + oldState.Presence ??= new(); + if (newState.Presence?.Events is not null) + oldState.Presence.Events.MergeStateEventLists(newState.Presence?.Events ?? new()); + + oldState.DeviceOneTimeKeysCount ??= new(); + if (newState.DeviceOneTimeKeysCount is not null) + foreach (var (key, value) in newState.DeviceOneTimeKeysCount) { + oldState.DeviceOneTimeKeysCount[key] = value; + } + + oldState.Rooms ??= new(); + if (newState.Rooms is not null) + oldState.Rooms = MergeRoomsDataStructure(oldState.Rooms, newState.Rooms); + + oldState.ToDevice ??= new(); + oldState.ToDevice.Events ??= new(); + if (newState.ToDevice?.Events is not null) + oldState.ToDevice.Events.MergeStateEventLists(newState.ToDevice?.Events ?? new()); + + oldState.DeviceLists ??= new(); + if (newState.DeviceLists?.Changed is not null) + foreach (var s in oldState.DeviceLists.Changed!) { + oldState.DeviceLists.Changed.Add(s); + } + if (newState.DeviceLists?.Left is not null) + foreach (var s in oldState.DeviceLists.Left!) { + oldState.DeviceLists.Left.Add(s); + } + + + return oldState; + } + +#region Merge rooms + + private SyncResponse.RoomsDataStructure MergeRoomsDataStructure(SyncResponse.RoomsDataStructure oldState, SyncResponse.RoomsDataStructure newState) { + oldState.Join ??= new(); + foreach (var (key, value) in newState.Join ?? new()) { + if (!oldState.Join.ContainsKey(key)) oldState.Join[key] = value; + else oldState.Join[key] = MergeJoinedRoomDataStructure(oldState.Join[key], value); + } + + oldState.Invite ??= new(); + foreach (var (key, value) in newState.Invite ?? new()) { + if (!oldState.Invite.ContainsKey(key)) oldState.Invite[key] = value; + else oldState.Invite[key] = MergeInvitedRoomDataStructure(oldState.Invite[key], value); + } + + oldState.Leave ??= new(); + foreach (var (key, value) in newState.Leave ?? new()) { + if (!oldState.Leave.ContainsKey(key)) oldState.Leave[key] = value; + else oldState.Leave[key] = MergeLeftRoomDataStructure(oldState.Leave[key], value); + if (oldState.Invite.ContainsKey(key)) oldState.Invite.Remove(key); + if (oldState.Join.ContainsKey(key)) oldState.Join.Remove(key); + } + + return oldState; + } + + private SyncResponse.RoomsDataStructure.LeftRoomDataStructure MergeLeftRoomDataStructure(SyncResponse.RoomsDataStructure.LeftRoomDataStructure oldData, + SyncResponse.RoomsDataStructure.LeftRoomDataStructure newData) { + oldData.AccountData ??= new(); + oldData.AccountData.Events ??= new(); + oldData.Timeline ??= new(); + oldData.Timeline.Events ??= new(); + oldData.State ??= new(); + oldData.State.Events ??= new(); + + if (newData.AccountData?.Events is not null) + oldData.AccountData.Events.MergeStateEventLists(newData.AccountData?.Events ?? new()); + + if (newData.Timeline?.Events is not null) + oldData.Timeline.Events.MergeStateEventLists(newData.Timeline?.Events ?? new()); + oldData.Timeline.Limited = newData.Timeline?.Limited ?? oldData.Timeline.Limited; + oldData.Timeline.PrevBatch = newData.Timeline?.PrevBatch ?? oldData.Timeline.PrevBatch; + + if (newData.State?.Events is not null) + oldData.State.Events.MergeStateEventLists(newData.State?.Events ?? new()); + + return oldData; + } + + private SyncResponse.RoomsDataStructure.InvitedRoomDataStructure MergeInvitedRoomDataStructure(SyncResponse.RoomsDataStructure.InvitedRoomDataStructure oldData, + SyncResponse.RoomsDataStructure.InvitedRoomDataStructure newData) { + oldData.InviteState ??= new(); + oldData.InviteState.Events ??= new(); + if (newData.InviteState?.Events is not null) + oldData.InviteState.Events.MergeStateEventLists(newData.InviteState?.Events ?? new()); + + return oldData; + } + + private SyncResponse.RoomsDataStructure.JoinedRoomDataStructure MergeJoinedRoomDataStructure(SyncResponse.RoomsDataStructure.JoinedRoomDataStructure oldData, + SyncResponse.RoomsDataStructure.JoinedRoomDataStructure newData) { + oldData.AccountData ??= new(); + oldData.AccountData.Events ??= new(); + oldData.Timeline ??= new(); + oldData.Timeline.Events ??= new(); + oldData.State ??= new(); + oldData.State.Events ??= new(); + oldData.Ephemeral ??= new(); + oldData.Ephemeral.Events ??= new(); + + if (newData.AccountData?.Events is not null) + oldData.AccountData.Events.MergeStateEventLists(newData.AccountData?.Events ?? new()); + + if (newData.Timeline?.Events is not null) + oldData.Timeline.Events.MergeStateEventLists(newData.Timeline?.Events ?? new()); + oldData.Timeline.Limited = newData.Timeline?.Limited ?? oldData.Timeline.Limited; + oldData.Timeline.PrevBatch = newData.Timeline?.PrevBatch ?? oldData.Timeline.PrevBatch; + + if (newData.State?.Events is not null) + oldData.State.Events.MergeStateEventLists(newData.State?.Events ?? new()); + + if (newData.Ephemeral?.Events is not null) + oldData.Ephemeral.Events.MergeStateEventLists(newData.Ephemeral?.Events ?? new()); + + oldData.UnreadNotifications ??= new(); + oldData.UnreadNotifications.HighlightCount = newData.UnreadNotifications?.HighlightCount ?? oldData.UnreadNotifications.HighlightCount; + oldData.UnreadNotifications.NotificationCount = newData.UnreadNotifications?.NotificationCount ?? oldData.UnreadNotifications.NotificationCount; + + oldData.Summary ??= new(); + oldData.Summary.Heroes = newData.Summary?.Heroes ?? oldData.Summary.Heroes; + oldData.Summary.JoinedMemberCount = newData.Summary?.JoinedMemberCount ?? oldData.Summary.JoinedMemberCount; + oldData.Summary.InvitedMemberCount = newData.Summary?.InvitedMemberCount ?? oldData.Summary.InvitedMemberCount; + + return oldData; + } + +#endregion +} diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index f70dd39..d5b0a77 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -12,19 +12,35 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverGeneric : RemoteHomeServer { - public AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : base(baseUrl) { - AccessToken = accessToken.Trim(); - SyncHelper = new SyncHelper(this); - - _httpClient.Timeout = TimeSpan.FromMinutes(15); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); +public class AuthenticatedHomeserverGeneric(string baseUrl, string accessToken) : RemoteHomeServer(baseUrl) { + public static async Task Create(string baseUrl, string accessToken) where T : AuthenticatedHomeserverGeneric { + var instance = Activator.CreateInstance(typeof(T), baseUrl, accessToken) as T + ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}"); + instance._httpClient = new() { + BaseAddress = new Uri(await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl) + ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromMinutes(15), + DefaultRequestHeaders = { + Authorization = new AuthenticationHeaderValue("Bearer", accessToken) + } + }; + instance.WhoAmI = await instance._httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"); + return instance; } - public virtual SyncHelper SyncHelper { get; init; } - private WhoAmIResponse? _whoAmI; + // Activator.CreateInstance(baseUrl, accessToken) { + // _httpClient = new() { + // BaseAddress = new Uri(await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl) + // ?? throw new InvalidOperationException("Failed to resolve homeserver")), + // Timeout = TimeSpan.FromMinutes(15), + // DefaultRequestHeaders = { + // Authorization = new AuthenticationHeaderValue("Bearer", accessToken) + // } + // } + // }; + - public WhoAmIResponse? WhoAmI => _whoAmI ??= _httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami").Result; + public WhoAmIResponse? WhoAmI { get; set; } public string UserId => WhoAmI.UserId; // public virtual async Task WhoAmI() { @@ -33,9 +49,9 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { // return _whoAmI; // } - public virtual string AccessToken { get; set; } + public string AccessToken { get; set; } = accessToken; - public virtual GenericRoom GetRoom(string roomId) { + public GenericRoom GetRoom(string roomId) { if (roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId)); return new GenericRoom(this, roomId); } @@ -112,7 +128,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeServer { #region Account Data - public virtual async Task GetAccountData(string key) { + public virtual async Task GetAccountDataAsync(string key) { // var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}"); // if (!res.IsSuccessStatusCode) { // Console.WriteLine($"Failed to get account data: {await res.Content.ReadAsStringAsync()}"); diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index d10c837..798349a 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -1,3 +1,4 @@ +using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; @@ -10,13 +11,18 @@ using LibMatrix.Services; namespace LibMatrix.Homeservers; public class RemoteHomeServer(string baseUrl) { + public static async Task Create(string baseUrl) => + new(baseUrl) { + _httpClient = new() { + BaseAddress = new Uri(await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl) + ?? throw new InvalidOperationException("Failed to resolve homeserver")), + Timeout = TimeSpan.FromSeconds(120) + } + }; private Dictionary _profileCache { get; set; } = new(); - public string BaseUrl { get; } = baseUrl.Trim(); - public MatrixHttpClient _httpClient { get; set; } = new() { - BaseAddress = new Uri(new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl).Result ?? throw new InvalidOperationException("Failed to resolve homeserver")), - Timeout = TimeSpan.FromSeconds(120) - }; + public string BaseUrl { get; } = baseUrl; + public MatrixHttpClient _httpClient { get; set; } public async Task GetProfileAsync(string mxid) { if (mxid is null) throw new ArgumentNullException(nameof(mxid)); diff --git a/LibMatrix/Interfaces/EventContent.cs b/LibMatrix/Interfaces/EventContent.cs new file mode 100644 index 0000000..b21cfc7 --- /dev/null +++ b/LibMatrix/Interfaces/EventContent.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Interfaces; + +public abstract class EventContent { + [JsonPropertyName("m.relates_to")] + public MessageRelatesTo? RelatesTo { get; set; } + + [JsonPropertyName("m.new_content")] + public EventContent? NewContent { get; set; } + + public class MessageRelatesTo { + [JsonPropertyName("m.in_reply_to")] + public EventInReplyTo? InReplyTo { get; set; } + + + + public abstract class EventInReplyTo { + [JsonPropertyName("event_id")] + public string EventId { get; set; } + + [JsonPropertyName("rel_type")] + public string RelType { get; set; } + } + } +} diff --git a/LibMatrix/Interfaces/IStateEventType.cs b/LibMatrix/Interfaces/IStateEventType.cs deleted file mode 100644 index b187970..0000000 --- a/LibMatrix/Interfaces/IStateEventType.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace LibMatrix.Interfaces; - -public abstract class EventContent { - [JsonPropertyName("m.relates_to")] - public MessageRelatesTo? RelatesTo { get; set; } - - [JsonPropertyName("m.new_content")] - public EventContent? NewContent { get; set; } - - public abstract class MessageRelatesTo { - [JsonPropertyName("m.in_reply_to")] - public EventInReplyTo? InReplyTo { get; set; } - - - - public abstract class EventInReplyTo { - [JsonPropertyName("event_id")] - public string EventId { get; set; } - } - } -} diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs index 511b3da..1ad590f 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; +using LibMatrix.EventTypes; using LibMatrix.EventTypes.Spec.State; using LibMatrix.Helpers; using LibMatrix.Homeservers; diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs index eb53c0a..07b1601 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs @@ -23,7 +23,7 @@ public class LoginResponse { public string UserId { get; set; } = null!; public async Task GetAuthenticatedHomeserver(string? proxy = null) { - return new AuthenticatedHomeserverGeneric(proxy ?? await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver), AccessToken); + return await AuthenticatedHomeserverGeneric.Create(proxy ?? await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver), AccessToken); } } public class LoginRequest { diff --git a/LibMatrix/Responses/StateEventResponse.cs b/LibMatrix/Responses/StateEventResponse.cs deleted file mode 100644 index 7ca6bab..0000000 --- a/LibMatrix/Responses/StateEventResponse.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; - -namespace LibMatrix.Responses; - -public class StateEventResponse : StateEvent { - [JsonPropertyName("origin_server_ts")] - public ulong OriginServerTs { get; set; } - - [JsonPropertyName("room_id")] - public string RoomId { get; set; } - - [JsonPropertyName("sender")] - public string Sender { get; set; } - - [JsonPropertyName("unsigned")] - public UnsignedData? Unsigned { get; set; } - - [JsonPropertyName("event_id")] - public string EventId { get; set; } - - [JsonPropertyName("user_id")] - public string UserId { get; set; } - - [JsonPropertyName("replaces_state")] - public new string ReplacesState { get; set; } - - public class UnsignedData { - [JsonPropertyName("age")] - public ulong? Age { get; set; } - - [JsonPropertyName("redacted_because")] - public object? RedactedBecause { get; set; } - - [JsonPropertyName("transaction_id")] - public string? TransactionId { get; set; } - - [JsonPropertyName("replaces_state")] - public string? ReplacesState { get; set; } - - [JsonPropertyName("prev_sender")] - public string? PrevSender { get; set; } - - [JsonPropertyName("prev_content")] - public JsonObject? PrevContent { get; set; } - } -} - -public class ChunkedStateEventResponse { - [JsonPropertyName("chunk")] - public List? Chunk { get; set; } -} diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs new file mode 100644 index 0000000..39cb38f --- /dev/null +++ b/LibMatrix/Responses/SyncResponse.cs @@ -0,0 +1,118 @@ +using System.Text.Json.Serialization; +using LibMatrix.Helpers; + +namespace LibMatrix.Responses; + +public class SyncResponse { + [JsonPropertyName("next_batch")] + public string NextBatch { get; set; } = null!; + + [JsonPropertyName("account_data")] + public EventList? AccountData { get; set; } + + [JsonPropertyName("presence")] + public PresenceDataStructure? Presence { get; set; } + + [JsonPropertyName("device_one_time_keys_count")] + public Dictionary? DeviceOneTimeKeysCount { get; set; } = null!; + + [JsonPropertyName("rooms")] + public RoomsDataStructure? Rooms { get; set; } + + [JsonPropertyName("to_device")] + public EventList? ToDevice { get; set; } + + [JsonPropertyName("device_lists")] + public DeviceListsDataStructure? DeviceLists { get; set; } + + public class DeviceListsDataStructure { + [JsonPropertyName("changed")] + public List? Changed { get; set; } + + [JsonPropertyName("left")] + public List? Left { get; set; } + } + + // supporting classes + public class PresenceDataStructure { + [JsonPropertyName("events")] + public List Events { get; set; } = new(); + } + + public class RoomsDataStructure { + [JsonPropertyName("join")] + public Dictionary? Join { get; set; } + + [JsonPropertyName("invite")] + public Dictionary? Invite { get; set; } + + [JsonPropertyName("leave")] + public Dictionary? Leave { get; set; } + + public class LeftRoomDataStructure { + [JsonPropertyName("account_data")] + public EventList AccountData { get; set; } + + [JsonPropertyName("timeline")] + public JoinedRoomDataStructure.TimelineDataStructure? Timeline { get; set; } + + [JsonPropertyName("state")] + public EventList State { get; set; } + } + + public class JoinedRoomDataStructure { + [JsonPropertyName("timeline")] + public TimelineDataStructure? Timeline { get; set; } + + [JsonPropertyName("state")] + public EventList? State { get; set; } + + [JsonPropertyName("account_data")] + public EventList? AccountData { get; set; } + + [JsonPropertyName("ephemeral")] + public EventList? Ephemeral { get; set; } + + [JsonPropertyName("unread_notifications")] + public UnreadNotificationsDataStructure? UnreadNotifications { get; set; } + + [JsonPropertyName("summary")] + public SummaryDataStructure? Summary { get; set; } + + public class TimelineDataStructure { + [JsonPropertyName("events")] + public List? Events { get; set; } + + [JsonPropertyName("prev_batch")] + public string? PrevBatch { get; set; } + + [JsonPropertyName("limited")] + public bool? Limited { get; set; } + } + + public class UnreadNotificationsDataStructure { + [JsonPropertyName("notification_count")] + public int NotificationCount { get; set; } + + [JsonPropertyName("highlight_count")] + public int HighlightCount { get; set; } + } + + public class SummaryDataStructure { + [JsonPropertyName("m.heroes")] + public List Heroes { get; set; } + + [JsonPropertyName("m.invited_member_count")] + public int InvitedMemberCount { get; set; } + + [JsonPropertyName("m.joined_member_count")] + public int JoinedMemberCount { get; set; } + } + } + + public class InvitedRoomDataStructure { + [JsonPropertyName("invite_state")] + public EventList? InviteState { get; set; } + } + } +} diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index 78a0873..75cb5f3 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -118,16 +118,16 @@ public class GenericRoom { #region Utility shortcuts - public async Task SendMessageEventAsync(RoomMessageEventContent content) => + public async Task SendMessageEventAsync(RoomMessageEventContent content) => await SendTimelineEventAsync("m.room.message", content); - public async Task> GetAliasesAsync() { + public async Task?> GetAliasesAsync() { var res = await GetStateAsync("m.room.aliases"); return res.Aliases; } - public async Task GetCanonicalAliasAsync() => - await GetStateAsync("m.room.canonical_alias"); + public async Task GetCanonicalAliasAsync() => + await GetStateAsync("m.room.canonical_alias"); public async Task GetTopicAsync() => await GetStateAsync("m.room.topic"); @@ -135,16 +135,16 @@ public class GenericRoom { public async Task GetAvatarUrlAsync() => await GetStateAsync("m.room.avatar"); - public async Task GetJoinRuleAsync() => - await GetStateAsync("m.room.join_rules"); + public async Task GetJoinRuleAsync() => + await GetStateAsync("m.room.join_rules"); - public async Task GetHistoryVisibilityAsync() => - await GetStateAsync("m.room.history_visibility"); + public async Task GetHistoryVisibilityAsync() => + await GetStateAsync("m.room.history_visibility"); - public async Task GetGuestAccessAsync() => - await GetStateAsync("m.room.guest_access"); + public async Task GetGuestAccessAsync() => + await GetStateAsync("m.room.guest_access"); - public async Task GetCreateEventAsync() => + public async Task GetCreateEventAsync() => await GetStateAsync("m.room.create"); public async Task GetRoomType() { @@ -177,24 +177,23 @@ public class GenericRoom { await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban", new UserIdAndReason { UserId = userId }); - public async Task SendStateEventAsync(string eventType, object content) => + public async Task SendStateEventAsync(string eventType, object content) => await (await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content)) .Content.ReadFromJsonAsync(); - public async Task SendStateEventAsync(string eventType, string stateKey, object content) => + public async Task SendStateEventAsync(string eventType, string stateKey, object content) => await (await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}/{stateKey}", content)) .Content.ReadFromJsonAsync(); - public async Task SendTimelineEventAsync(string eventType, EventContent content) { + public async Task SendTimelineEventAsync(string eventType, EventContent content) { var res = await _httpClient.PutAsJsonAsync( $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(), content, new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); - var resu = await res.Content.ReadFromJsonAsync(); - return resu; + return await res.Content.ReadFromJsonAsync(); } - public async Task SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file") { + public async Task SendFileAsync(string fileName, Stream fileStream, string messageType = "m.file") { var url = await Homeserver.UploadFile(fileName, fileStream); var content = new RoomMessageEventContent() { MessageType = messageType, @@ -205,7 +204,7 @@ public class GenericRoom { return await SendTimelineEventAsync("m.room.message", content); } - public async Task GetRoomAccountDataAsync(string key) { + public async Task GetRoomAccountDataAsync(string key) { var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}"); if (!res.IsSuccessStatusCode) { Console.WriteLine($"Failed to get room account data: {await res.Content.ReadAsStringAsync()}"); diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs index 666d2a2..1f3bd37 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -35,20 +35,15 @@ public class HomeserverProviderService { } var domain = proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); - var hc = new MatrixHttpClient { BaseAddress = new Uri(domain) }; AuthenticatedHomeserverGeneric hs; if (true) { - hs = new AuthenticatedHomeserverMxApiExtended(homeserver, accessToken); + hs = await AuthenticatedHomeserverGeneric.Create(homeserver, accessToken); } else { - hs = new AuthenticatedHomeserverGeneric(homeserver, accessToken); + hs = await AuthenticatedHomeserverGeneric.Create(homeserver, accessToken); } - hs._httpClient = hc; - hs._httpClient.Timeout = TimeSpan.FromMinutes(15); - hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - // (() => hs.WhoAmI) = (await hs._httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"))!; lock(_authenticatedHomeServerCache) @@ -59,7 +54,7 @@ public class HomeserverProviderService { } public async Task GetRemoteHomeserver(string homeserver, string? proxy = null) { - var hs = new RemoteHomeServer(proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)); + var hs = await RemoteHomeServer.Create(proxy ?? await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver)); // hs._httpClient.Dispose(); // hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; // hs._httpClient.Timeout = TimeSpan.FromSeconds(120); diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs index 685724b..75545db 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs @@ -12,6 +12,9 @@ public class HomeserverResolverService(ILogger? logge private static readonly Dictionary _wellKnownSemaphores = new(); public async Task ResolveHomeserverFromWellKnown(string homeserver) { + if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); + if(_wellKnownCache.TryGetValue(homeserver, out var known)) return known; + logger?.LogInformation("Resolving homeserver: {}", homeserver); var res = await _resolveHomeserverFromWellKnown(homeserver); if (!res.StartsWith("http")) res = "https://" + res; if (res.EndsWith(":443")) res = res[..^4]; @@ -21,6 +24,7 @@ public class HomeserverResolverService(ILogger? logge private async Task _resolveHomeserverFromWellKnown(string homeserver) { if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); var sem = _wellKnownSemaphores.GetOrCreate(homeserver, _ => new SemaphoreSlim(1, 1)); + if(_wellKnownCache.TryGetValue(homeserver, out var wellKnown)) return wellKnown; await sem.WaitAsync(); if (_wellKnownCache.TryGetValue(homeserver, out var known)) { sem.Release(); diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs index b42bd64..c51fadb 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs @@ -39,8 +39,11 @@ public class StateEvent { public EventContent TypedContent { get { + if(Type == "m.receipt") { + return null!; + } try { - return (EventContent) RawContent.Deserialize(GetType)!; + return (EventContent)RawContent.Deserialize(GetType)!; } catch (JsonException e) { Console.WriteLine(e); @@ -122,6 +125,61 @@ public class StateEvent { public string cdtype => TypedContent.GetType().Name; } +public class StateEventResponse : StateEvent { + [JsonPropertyName("origin_server_ts")] + public ulong OriginServerTs { get; set; } + + [JsonPropertyName("room_id")] + public string RoomId { get; set; } + + [JsonPropertyName("sender")] + public string Sender { get; set; } + + [JsonPropertyName("unsigned")] + public UnsignedData? Unsigned { get; set; } + + [JsonPropertyName("event_id")] + public string EventId { get; set; } + + [JsonPropertyName("user_id")] + public string UserId { get; set; } + + [JsonPropertyName("replaces_state")] + public new string ReplacesState { get; set; } + + public class UnsignedData { + [JsonPropertyName("age")] + public ulong? Age { get; set; } + + [JsonPropertyName("redacted_because")] + public object? RedactedBecause { get; set; } + + [JsonPropertyName("transaction_id")] + public string? TransactionId { get; set; } + + [JsonPropertyName("replaces_state")] + public string? ReplacesState { get; set; } + + [JsonPropertyName("prev_sender")] + public string? PrevSender { get; set; } + + [JsonPropertyName("prev_content")] + public JsonObject? PrevContent { get; set; } + } +} + +public class EventList { + [JsonPropertyName("events")] + public List? Events { get; set; } = new(); +} + +public class ChunkedStateEventResponse { + [JsonPropertyName("chunk")] + public List? Chunk { get; set; } = new(); +} + +#region Unused code + /* public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoResolver { @@ -150,3 +208,5 @@ public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoR } } */ + +#endregion -- cgit 1.5.1