about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.idea/.idea.LibMatrix/.idea/.name1
m---------ArcaneLibs0
-rw-r--r--LibMatrix.EventTypes/LibMatrix.EventTypes.csproj2
-rw-r--r--LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs17
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs17
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs4
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs4
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs14
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs8
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs2
-rw-r--r--LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs2
-rw-r--r--LibMatrix.sln224
-rw-r--r--LibMatrix/EventIdResponse.cs2
-rw-r--r--LibMatrix/Extensions/CanonicalJsonSerializer.cs16
-rw-r--r--LibMatrix/Extensions/JsonElementExtensions.cs1
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Multi.cs11
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Single.cs32
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs7
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs77
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverHSE.cs16
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs2
-rw-r--r--LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs58
-rw-r--r--LibMatrix/Homeservers/FederationClient.cs5
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/SynapseAdminRoomDeleteRequest.cs31
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomMemberListResult.cs11
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminUserRedactIdResponse.cs22
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseUserMediaResult.cs40
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs207
-rw-r--r--LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminUserCleanupExecutor.cs27
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs89
-rw-r--r--LibMatrix/Homeservers/UserInteractiveAuthClient.cs14
-rw-r--r--LibMatrix/LibMatrix.csproj6
-rw-r--r--LibMatrix/LibMatrixException.cs3
-rw-r--r--LibMatrix/MatrixException.cs5
-rw-r--r--LibMatrix/MxcUri.cs43
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs19
-rw-r--r--LibMatrix/Responses/LoginResponse.cs19
-rw-r--r--LibMatrix/Responses/SyncResponse.cs5
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs24
-rw-r--r--LibMatrix/Services/HomeserverProviderService.cs13
-rw-r--r--LibMatrix/Services/HomeserverResolverService.cs41
-rw-r--r--LibMatrix/Services/ServiceInstaller.cs12
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs49
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs91
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs52
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs42
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs38
-rw-r--r--LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs44
-rw-r--r--LibMatrix/StateEvent.cs4
-rw-r--r--LibMatrix/Utilities/CommonSyncFilters.cs2
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs2
-rw-r--r--Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs5
-rw-r--r--Tests/LibMatrix.Tests/Config.cs2
-rw-r--r--Tests/LibMatrix.Tests/Fixtures/TestFixture.cs1
-rw-r--r--Tests/LibMatrix.Tests/LibMatrix.Tests.csproj14
-rw-r--r--Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs1
-rw-r--r--Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs4
-rw-r--r--Tests/LibMatrix.Tests/Tests/HomeserverResolverTests/ClientWellKnownResolverTests.cs41
-rw-r--r--Tests/LibMatrix.Tests/Tests/LegacyHomeserverResolverTests.cs (renamed from Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs)11
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs2
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs2
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs6
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs6
-rw-r--r--Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs6
-rw-r--r--Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj4
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/Commands/DbgAniRainbowTest.cs1
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/Commands/PingCommand.cs13
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs2
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs9
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs125
-rw-r--r--Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj6
-rw-r--r--Utilities/LibMatrix.DevTestBot/Program.cs2
-rw-r--r--Utilities/LibMatrix.DevTestBot/appsettings.json4
-rw-r--r--Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj6
-rw-r--r--Utilities/LibMatrix.E2eeTestKit/Pages/CSTJTest.razor1
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/AuthController.cs13
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs93
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/HEAdmin/HEAdminController.cs18
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/HEClient/HEClientController.cs34
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/HEDebug/HEDebugController.cs34
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs6
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs14
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs2
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs28
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs3
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs50
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/AccountDataController.cs4
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/FilterController.cs4
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs4
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs6
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs7
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/WellKnownController.cs64
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj6
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Program.cs9
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs18
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/MediaStore.cs16
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs6
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs69
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs20
-rw-r--r--Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj6
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs18
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Commands/AliassesCommand.cs1
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs3
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs4
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj6
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs1
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs3
-rw-r--r--Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs6
116 files changed, 1679 insertions, 664 deletions
diff --git a/.idea/.idea.LibMatrix/.idea/.name b/.idea/.idea.LibMatrix/.idea/.name
deleted file mode 100644

index 2f382b8..0000000 --- a/.idea/.idea.LibMatrix/.idea/.name +++ /dev/null
@@ -1 +0,0 @@ -LibMatrix \ No newline at end of file diff --git a/ArcaneLibs b/ArcaneLibs -Subproject 92e32f8d89b522a974368f6b21c9c0c1edcfa40 +Subproject 2956a7ce4e8d12034322a91b6afa449e7035485 diff --git a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
index aaa37e4..647b68f 100644 --- a/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj +++ b/LibMatrix.EventTypes/LibMatrix.EventTypes.csproj
@@ -7,7 +7,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20241122-053825" Condition="'$(Configuration)' == 'Release'"/> + <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250307-202359" Condition="'$(Configuration)' == 'Release'" /> <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/> </ItemGroup> diff --git a/LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs b/LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
index 494936d..a7d431c 100644 --- a/LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs +++ b/LibMatrix.EventTypes/Spec/Ephemeral/RoomTypingEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.Ephemeral; [MatrixEvent(EventName = EventId)] public class RoomTypingEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
index 0569477..86950e5 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -43,7 +43,7 @@ public abstract class PolicyRuleEventContent : EventContent { [FriendlyName(Name = "Entity")] public string? Entity { get; set; } - private bool init; + // private bool init; /// <summary> /// Reason this user is banned @@ -93,13 +93,18 @@ public abstract class PolicyRuleEventContent : EventContent { public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray())); - public Regex GetEntityRegex() => new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); + public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); public bool EntityMatches(string entity) => - Entity == entity - || (Entity.Contains("*") || Entity.Contains("?") - ? GetEntityRegex().IsMatch(entity) - : entity == Entity); + Entity != null + && ( + Entity == entity + || ( + Entity.Contains("*") || Entity.Contains("?") + ? GetEntityRegex()!.IsMatch(entity) + : entity == Entity + ) + ); } public static class PolicyRecommendationTypes { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
index 93f13ac..ee3234c 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCanonicalAliasEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomCanonicalAliasEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
index f26b8e5..37b831a 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
@@ -1,8 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Deserialization, public API")] +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Deserialization, public API")] public class RoomCreateEventContent : EventContent { public const string EventId = "m.room.create"; @@ -20,12 +23,12 @@ public class RoomCreateEventContent : EventContent { [JsonPropertyName("type")] public string? Type { get; set; } +} - public class RoomCreatePredecessor { - [JsonPropertyName("room_id")] - public string? RoomId { get; set; } +public class RoomCreatePredecessor { + [JsonPropertyName("room_id")] + public string? RoomId { get; set; } - [JsonPropertyName("event_id")] - public string? EventId { get; set; } - } + [JsonPropertyName("event_id")] + public string? EventId { get; set; } } \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
index b49abfa..16209f0 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomEncryptionEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomEncryptionEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
index a7811bf..1ba5a3f 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomGuestAccessEventContent.cs
@@ -1,13 +1,13 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomGuestAccessEventContent : EventContent { public const string EventId = "m.room.guest_access"; [JsonPropertyName("guest_access")] - public string GuestAccess { get; set; } + public required string GuestAccess { get; set; } [JsonIgnore] public bool IsGuestAccessEnabled { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
index 7676dad..16cfcb0 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomHistoryVisibilityEventContent.cs
@@ -1,11 +1,11 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomHistoryVisibilityEventContent : EventContent { public const string EventId = "m.room.history_visibility"; [JsonPropertyName("history_visibility")] - public string HistoryVisibility { get; set; } + public required string HistoryVisibility { get; set; } } \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs
index 349c8a7..03d994d 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomJoinRulesEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomJoinRulesEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
index b2d5596..b034425 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomMemberEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomMemberEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
index 3ea5730..415c675 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomNameEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomNameEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
index b4474e9..7eee605 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPinnedEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] public class RoomPinnedEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
index eb156b3..22fa3b7 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] @@ -20,7 +20,7 @@ public class RoomPowerLevelEventContent : EventContent { public long? Kick { get; set; } = 50; [JsonPropertyName("notifications")] - public NotificationsPL? NotificationsPl { get; set; } // = null!; + public NotificationsPowerLevels? NotificationsPl { get; set; } [JsonPropertyName("redact")] public long? Redact { get; set; } = 50; @@ -29,10 +29,10 @@ public class RoomPowerLevelEventContent : EventContent { public long? StateDefault { get; set; } = 50; [JsonPropertyName("events")] - public Dictionary<string, long>? Events { get; set; } // = null!; + public Dictionary<string, long>? Events { get; set; } [JsonPropertyName("users")] - public Dictionary<string, long>? Users { get; set; } // = null!; + public Dictionary<string, long>? Users { get; set; } [JsonPropertyName("users_default")] public long? UsersDefault { get; set; } = 0; @@ -42,19 +42,19 @@ public class RoomPowerLevelEventContent : EventContent { [JsonPropertyName("historical")] public long Historical { get; set; } // = 50; - public class NotificationsPL { + public class NotificationsPowerLevels { [JsonPropertyName("room")] public long Room { get; set; } = 50; } public bool IsUserAdmin(string userId) { ArgumentNullException.ThrowIfNull(userId); - return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value); + return GetUserPowerLevel(userId) >= Events?.Max(x => x.Value); } public bool UserHasTimelinePermission(string userId, string eventType) { ArgumentNullException.ThrowIfNull(userId); - return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault ?? 0); + return GetUserPowerLevel(userId) >= Events?.GetValueOrDefault(eventType, EventsDefault ?? 0); } public bool UserHasStatePermission(string userId, string eventType, bool log = false) { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
index be83e37..c492250 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
@@ -1,16 +1,16 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] -public class RoomServerACLEventContent : EventContent { +public class RoomServerAclEventContent : EventContent { public const string EventId = "m.room.server_acl"; [JsonPropertyName("allow")] - public List<string>? Allow { get; set; } // = null!; + public List<string>? Allow { get; set; } [JsonPropertyName("deny")] - public List<string>? Deny { get; set; } // = null!; + public List<string>? Deny { get; set; } [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
index 92fa75d..065c976 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomTopicEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] [MatrixEvent(EventName = "org.matrix.msc3765.topic", Legacy = true)] diff --git a/LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs b/LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs
index d233be4..cd0f1f5 100644 --- a/LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Space/SpaceChildEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.Space; [MatrixEvent(EventName = EventId)] public class SpaceChildEventContent : EventContent { diff --git a/LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs b/LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs
index 2ab79a4..f50797e 100644 --- a/LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Space/SpaceParentEventContent.cs
@@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace LibMatrix.EventTypes.Spec.State; +namespace LibMatrix.EventTypes.Spec.State.Space; [MatrixEvent(EventName = EventId)] public class SpaceParentEventContent : EventContent { diff --git a/LibMatrix.sln b/LibMatrix.sln
index 3294f77..5feeeee 100644 --- a/LibMatrix.sln +++ b/LibMatrix.sln
@@ -3,49 +3,47 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{2A07D7DA-7B8F-432D-8AD3-9679B58A7C19}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{32C0F9AC-AF7D-4476-A269-99ACA000EF9F}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{A6345ECE-4C5E-400F-9130-886E343BF314}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{93D00F03-02FF-4C2A-8917-6863D6E633D9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.MxApiExtensions", "LibMatrix.MxApiExtensions\LibMatrix.MxApiExtensions.csproj", "{32D9616B-91BB-4B43-97C6-2C3840C12EA6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{35DF9A1A-D988-4225-AFA3-06BB8EDEB559}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{51F770AF-C0C6-4247-A358-82DF323F473D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{8A8E8B67-9351-471E-A502-AF7FAE596CB4}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BFE16D8E-EFC5-49F6-9854-DB001309B3B4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Tests", "ArcaneLibs\ArcaneLibs.Tests\ArcaneLibs.Tests.csproj", "{5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{345934FF-CA81-4A4B-B137-9F198102C65F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{567DABFB-611E-4779-9F39-1D4A5B8F0247}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestDataGenerator", "Tests\TestDataGenerator\TestDataGenerator.csproj", "{0B9B34D1-9362-45A9-9C21-816FD6959110}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{28AFDFEF-7597-4450-B999-87C22C24DCD8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{4D9B5227-48DC-4A30-9263-AFB51DC01ABB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{F80D5395-28E3-4C34-9662-A890A215DDA2}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ArcaneLibs", "ArcaneLibs", "{01A126FE-9D50-40F2-817B-E55F4065EA76}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{90A38896-993A-49F1-903B-8C989D8C9B3A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs", "ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj", "{13A797D1-7E13-4789-A167-8628B1641AC0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix", "LibMatrix\LibMatrix.csproj", "{6AE504F8-734B-456B-8BEA-DE37639CB3A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.EventTypes", "LibMatrix.EventTypes\LibMatrix.EventTypes.csproj", "{CD13665B-B964-4AB0-991B-12F067B16DA3}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{AAAA5609-D8C1-401E-BB5C-724EFE3955FB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Tests\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Tests", "Tests\LibMatrix.Tests\LibMatrix.Tests.csproj", "{7E15D295-B938-409C-98F6-A22ABC7F3005}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Blazor.Components", "ArcaneLibs\ArcaneLibs.Blazor.Components\ArcaneLibs.Blazor.Components.csproj", "{ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Utilities", "Utilities", "{FBC6F613-4E0B-4A90-8854-31887A796EEF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Legacy", "ArcaneLibs\ArcaneLibs.Legacy\ArcaneLibs.Legacy.csproj", "{F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DebugDataValidationApi", "Utilities\LibMatrix.DebugDataValidationApi\LibMatrix.DebugDataValidationApi.csproj", "{D632374A-70A1-4954-8F2D-C0B511A24426}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Logging", "ArcaneLibs\ArcaneLibs.Logging\ArcaneLibs.Logging.csproj", "{711D1579-9228-47BA-9CB3-C237F7E8F403}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{91E41EE3-687B-4CFD-94C4-028D09BACFAB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.StringNormalisation", "ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj", "{87270607-F95C-4EED-AE69-57666863EFDF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{4D411F2D-A97D-4485-A318-ED98B96B6CF6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.Timings", "ArcaneLibs\ArcaneLibs.Timings\ArcaneLibs.Timings.csproj", "{5EE6E02C-A63F-4864-91F3-7375B4C50189}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "Utilities\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{7F36D200-FE91-4761-B681-BEE091FB953F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.JsonSerializerContextGenerator", "Utilities\LibMatrix.JsonSerializerContextGenerator\LibMatrix.JsonSerializerContextGenerator.csproj", "{69CCB780-50CC-4E75-B794-932E44ACA80F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLib.Tests", "ArcaneLibs\ArcaneLib.Tests\ArcaneLib.Tests.csproj", "{E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.TestDataGenerator", "Utilities\LibMatrix.TestDataGenerator\LibMatrix.TestDataGenerator.csproj", "{C087F8AC-B438-4980-9A79-1E16D0CFB67A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.E2eeTestKit", "Utilities\LibMatrix.E2eeTestKit\LibMatrix.E2eeTestKit.csproj", "{A1066F8F-CE8A-4F60-9EFB-553C9E794435}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.Utilities.Bot", "Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj", "{E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -56,99 +54,95 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2A07D7DA-7B8F-432D-8AD3-9679B58A7C19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A07D7DA-7B8F-432D-8AD3-9679B58A7C19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A07D7DA-7B8F-432D-8AD3-9679B58A7C19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A07D7DA-7B8F-432D-8AD3-9679B58A7C19}.Release|Any CPU.Build.0 = Release|Any CPU - {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32D9616B-91BB-4B43-97C6-2C3840C12EA6}.Release|Any CPU.Build.0 = Release|Any CPU - {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Debug|Any CPU.Build.0 = Debug|Any CPU - {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35DF9A1A-D988-4225-AFA3-06BB8EDEB559}.Release|Any CPU.Build.0 = Release|Any CPU - {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7}.Release|Any CPU.Build.0 = Release|Any CPU - {345934FF-CA81-4A4B-B137-9F198102C65F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {345934FF-CA81-4A4B-B137-9F198102C65F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {345934FF-CA81-4A4B-B137-9F198102C65F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {345934FF-CA81-4A4B-B137-9F198102C65F}.Release|Any CPU.Build.0 = Release|Any CPU - {0B9B34D1-9362-45A9-9C21-816FD6959110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B9B34D1-9362-45A9-9C21-816FD6959110}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B9B34D1-9362-45A9-9C21-816FD6959110}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B9B34D1-9362-45A9-9C21-816FD6959110}.Release|Any CPU.Build.0 = Release|Any CPU - {4D9B5227-48DC-4A30-9263-AFB51DC01ABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4D9B5227-48DC-4A30-9263-AFB51DC01ABB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D9B5227-48DC-4A30-9263-AFB51DC01ABB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4D9B5227-48DC-4A30-9263-AFB51DC01ABB}.Release|Any CPU.Build.0 = Release|Any CPU - {13A797D1-7E13-4789-A167-8628B1641AC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {13A797D1-7E13-4789-A167-8628B1641AC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {13A797D1-7E13-4789-A167-8628B1641AC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {13A797D1-7E13-4789-A167-8628B1641AC0}.Release|Any CPU.Build.0 = Release|Any CPU - {CD13665B-B964-4AB0-991B-12F067B16DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD13665B-B964-4AB0-991B-12F067B16DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD13665B-B964-4AB0-991B-12F067B16DA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD13665B-B964-4AB0-991B-12F067B16DA3}.Release|Any CPU.Build.0 = Release|Any CPU - {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2}.Release|Any CPU.Build.0 = Release|Any CPU - {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C}.Release|Any CPU.Build.0 = Release|Any CPU - {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD}.Release|Any CPU.Build.0 = Release|Any CPU - {711D1579-9228-47BA-9CB3-C237F7E8F403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {711D1579-9228-47BA-9CB3-C237F7E8F403}.Debug|Any CPU.Build.0 = Debug|Any CPU - {711D1579-9228-47BA-9CB3-C237F7E8F403}.Release|Any CPU.ActiveCfg = Release|Any CPU - {711D1579-9228-47BA-9CB3-C237F7E8F403}.Release|Any CPU.Build.0 = Release|Any CPU - {87270607-F95C-4EED-AE69-57666863EFDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87270607-F95C-4EED-AE69-57666863EFDF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87270607-F95C-4EED-AE69-57666863EFDF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87270607-F95C-4EED-AE69-57666863EFDF}.Release|Any CPU.Build.0 = Release|Any CPU - {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5EE6E02C-A63F-4864-91F3-7375B4C50189}.Release|Any CPU.Build.0 = Release|Any CPU - {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF}.Release|Any CPU.Build.0 = Release|Any CPU - {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF}.Release|Any CPU.Build.0 = Release|Any CPU - {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5}.Release|Any CPU.Build.0 = Release|Any CPU - {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1066F8F-CE8A-4F60-9EFB-553C9E794435}.Release|Any CPU.Build.0 = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93D00F03-02FF-4C2A-8917-6863D6E633D9}.Release|Any CPU.Build.0 = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB}.Release|Any CPU.Build.0 = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51F770AF-C0C6-4247-A358-82DF323F473D}.Release|Any CPU.Build.0 = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A8E8B67-9351-471E-A502-AF7FAE596CB4}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB}.Release|Any CPU.Build.0 = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {567DABFB-611E-4779-9F39-1D4A5B8F0247}.Release|Any CPU.Build.0 = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28AFDFEF-7597-4450-B999-87C22C24DCD8}.Release|Any CPU.Build.0 = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F80D5395-28E3-4C34-9662-A890A215DDA2}.Release|Any CPU.Build.0 = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90A38896-993A-49F1-903B-8C989D8C9B3A}.Release|Any CPU.Build.0 = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6AE504F8-734B-456B-8BEA-DE37639CB3A7}.Release|Any CPU.Build.0 = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E15D295-B938-409C-98F6-A22ABC7F3005}.Release|Any CPU.Build.0 = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D632374A-70A1-4954-8F2D-C0B511A24426}.Release|Any CPU.Build.0 = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91E41EE3-687B-4CFD-94C4-028D09BACFAB}.Release|Any CPU.Build.0 = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D411F2D-A97D-4485-A318-ED98B96B6CF6}.Release|Any CPU.Build.0 = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F36D200-FE91-4761-B681-BEE091FB953F}.Release|Any CPU.Build.0 = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69CCB780-50CC-4E75-B794-932E44ACA80F}.Release|Any CPU.Build.0 = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C087F8AC-B438-4980-9A79-1E16D0CFB67A}.Release|Any CPU.Build.0 = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {35DF9A1A-D988-4225-AFA3-06BB8EDEB559} = {A6345ECE-4C5E-400F-9130-886E343BF314} - {3ACF2613-E23F-42C2-925E-0BB4FC3AB1F7} = {A6345ECE-4C5E-400F-9130-886E343BF314} - {345934FF-CA81-4A4B-B137-9F198102C65F} = {BFE16D8E-EFC5-49F6-9854-DB001309B3B4} - {0B9B34D1-9362-45A9-9C21-816FD6959110} = {BFE16D8E-EFC5-49F6-9854-DB001309B3B4} - {4D9B5227-48DC-4A30-9263-AFB51DC01ABB} = {A6345ECE-4C5E-400F-9130-886E343BF314} - {13A797D1-7E13-4789-A167-8628B1641AC0} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {D44DB78D-9BAD-4AB6-A054-839ECA9D68D2} = {BFE16D8E-EFC5-49F6-9854-DB001309B3B4} - {ABAB8F42-A4BC-4ABF-AF1D-FDB40D87A91C} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {F94D35FF-E90E-4B86-B26E-A8E46EB54BDD} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {711D1579-9228-47BA-9CB3-C237F7E8F403} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {87270607-F95C-4EED-AE69-57666863EFDF} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {5EE6E02C-A63F-4864-91F3-7375B4C50189} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {7E7BBBDD-CE09-4298-B839-5EBDCA5D0DAF} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {E6BF8A7E-92F6-4761-A793-1EC709F7E2BF} = {01A126FE-9D50-40F2-817B-E55F4065EA76} - {6F4A4ABC-5E42-4293-80E6-0C38FE8C9EC5} = {A6345ECE-4C5E-400F-9130-886E343BF314} - {A1066F8F-CE8A-4F60-9EFB-553C9E794435} = {A6345ECE-4C5E-400F-9130-886E343BF314} + {93D00F03-02FF-4C2A-8917-6863D6E633D9} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {14CAF7A1-A5C3-4ED1-8BF3-20FC3447BFFB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {51F770AF-C0C6-4247-A358-82DF323F473D} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {8A8E8B67-9351-471E-A502-AF7FAE596CB4} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {5DE8D878-EEBD-44DF-A8FC-0B4866B807FB} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {567DABFB-611E-4779-9F39-1D4A5B8F0247} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {28AFDFEF-7597-4450-B999-87C22C24DCD8} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {F80D5395-28E3-4C34-9662-A890A215DDA2} = {32C0F9AC-AF7D-4476-A269-99ACA000EF9F} + {7E15D295-B938-409C-98F6-A22ABC7F3005} = {AAAA5609-D8C1-401E-BB5C-724EFE3955FB} + {D632374A-70A1-4954-8F2D-C0B511A24426} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {91E41EE3-687B-4CFD-94C4-028D09BACFAB} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {4D411F2D-A97D-4485-A318-ED98B96B6CF6} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {7F36D200-FE91-4761-B681-BEE091FB953F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {69CCB780-50CC-4E75-B794-932E44ACA80F} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {C087F8AC-B438-4980-9A79-1E16D0CFB67A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} + {E04E5F37-8F13-4000-ABA1-DAC0D795EA6A} = {FBC6F613-4E0B-4A90-8854-31887A796EEF} EndGlobalSection EndGlobal diff --git a/LibMatrix/EventIdResponse.cs b/LibMatrix/EventIdResponse.cs
index 4d715a4..6a04229 100644 --- a/LibMatrix/EventIdResponse.cs +++ b/LibMatrix/EventIdResponse.cs
@@ -4,5 +4,5 @@ namespace LibMatrix; public class EventIdResponse { [JsonPropertyName("event_id")] - public string EventId { get; set; } + public required string EventId { get; set; } } \ No newline at end of file diff --git a/LibMatrix/Extensions/CanonicalJsonSerializer.cs b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
index a6fbcf4..55a4b1a 100644 --- a/LibMatrix/Extensions/CanonicalJsonSerializer.cs +++ b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
@@ -1,19 +1,14 @@ using System.Collections.Frozen; using System.Reflection; -using System.Security.Cryptography; -using System.Text.Encodings.Web; using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; -using System.Text.Unicode; using ArcaneLibs.Extensions; namespace LibMatrix.Extensions; public static class CanonicalJsonSerializer { // TODO: Alphabetise dictionaries - private static JsonSerializerOptions _options => new() { + private static JsonSerializerOptions JsonOptions => new() { WriteIndented = false, Encoder = UnicodeJsonEncoder.Singleton, }; @@ -24,7 +19,7 @@ public static class CanonicalJsonSerializer { .ToFrozenSet(); private static JsonSerializerOptions MergeOptions(JsonSerializerOptions? inputOptions) { - var newOptions = _options; + var newOptions = JsonOptions; if (inputOptions == null) return newOptions; @@ -48,7 +43,7 @@ public static class CanonicalJsonSerializer { public static String Serialize<TValue>(TValue value, JsonSerializerOptions? options = null) { var newOptions = MergeOptions(options); - return System.Text.Json.JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + return JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... .SortProperties()! .CanonicalizeNumbers()! .ToJsonString(newOptions); @@ -58,13 +53,14 @@ public static class CanonicalJsonSerializer { } - public static String Serialize(object value, Type inputType, JsonSerializerOptions? options = null) => JsonSerializer.Serialize(value, inputType, _options); + public static String Serialize(object value, Type inputType, JsonSerializerOptions? options = null) => JsonSerializer.Serialize(value, inputType, JsonOptions); // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options); // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) #endregion - private static partial class JsonExtensions { + // ReSharper disable once UnusedType.Local + private static class JsonExtensions { public static Action<JsonTypeInfo> AlphabetizeProperties(Type type) { return typeInfo => { if (typeInfo.Kind != JsonTypeInfoKind.Object || !type.IsAssignableFrom(typeInfo.Type)) diff --git a/LibMatrix/Extensions/JsonElementExtensions.cs b/LibMatrix/Extensions/JsonElementExtensions.cs
index c4ed743..dfec95b 100644 --- a/LibMatrix/Extensions/JsonElementExtensions.cs +++ b/LibMatrix/Extensions/JsonElementExtensions.cs
@@ -126,6 +126,7 @@ public static class JsonElementExtensions { $"Encountered dictionary {field.Name} with key type {keyType.Name} and value type {valueType.Name}!"); return field.Value.EnumerateObject() + // TODO: use key.Value? .Where(key => !valueType.IsPrimitive && valueType != typeof(string)) .Aggregate(false, (current, key) => current | key.FindExtraJsonPropertyFieldsByValueKind(containerType, valueType) diff --git a/LibMatrix/Extensions/MatrixHttpClient.Multi.cs b/LibMatrix/Extensions/MatrixHttpClient.Multi.cs
index e7a2044..bf6fe63 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Multi.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Multi.cs
@@ -1,16 +1,5 @@ #define SINGLE_HTTPCLIENT // Use a single HttpClient instance for all MatrixHttpClient instances // #define SYNC_HTTPCLIENT // Only allow one request as a time, for debugging -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Net.Http.Headers; -using System.Reflection; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using ArcaneLibs; -using ArcaneLibs.Extensions; - namespace LibMatrix.Extensions; public static class HttpClientExtensions { diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index 0e6d467..a3ea409 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -5,12 +5,12 @@ using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Http.Headers; using System.Reflection; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using ArcaneLibs; using ArcaneLibs.Extensions; +using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests; namespace LibMatrix.Extensions; @@ -82,10 +82,11 @@ public class MatrixHttpClient { Console.WriteLine($"Sending {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.GetContentLength())})"); if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null"); - if (!request.RequestUri.IsAbsoluteUri) request.RequestUri = new Uri(BaseAddress, request.RequestUri); + if (!request.RequestUri.IsAbsoluteUri) + request.RequestUri = new Uri(BaseAddress ?? throw new InvalidOperationException("Relative URI passed, but no BaseAddress is specified!"), request.RequestUri); swWait.Stop(); var swExec = Stopwatch.StartNew(); - + foreach (var (key, value) in AdditionalQueryParameters) request.RequestUri = request.RequestUri.AddQuery(key, value); foreach (var (key, value) in DefaultRequestHeaders) { if (request.Headers.Contains(key)) continue; @@ -102,8 +103,16 @@ public class MatrixHttpClient { responseMessage = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); } catch (Exception e) { - Console.WriteLine( - $"Failed to send request {request.Method} {request.RequestUri} ({Util.BytesToString(request.GetContentLength())}):\n{e}"); + if (e is TaskCanceledException or TimeoutException) { + if (request.Method == HttpMethod.Get && !cancellationToken.IsCancellationRequested) { + await Task.Delay(Random.Shared.Next(500, 2500), cancellationToken); + request.ResetSendStatus(); + return await SendAsync(request, cancellationToken); + } + } + else if (!e.ToString().StartsWith("TypeError: NetworkError")) + Console.WriteLine( + $"Failed to send request {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)}):\n{e}"); throw; } #if SYNC_HTTPCLIENT @@ -156,7 +165,7 @@ public class MatrixHttpClient { if (!content.StartsWith('{')) throw new InvalidDataException("Encountered invalid data:\n" + content); //we have a matrix error - MatrixException? ex = null; + MatrixException? ex; try { ex = JsonSerializer.Deserialize<MatrixException>(content); } @@ -170,7 +179,7 @@ public class MatrixHttpClient { Debug.Assert(ex != null, nameof(ex) + " != null"); ex.RawContent = content; // Console.WriteLine($"Failed to send request: {ex}"); - if (ex?.RetryAfterMs is null) throw ex!; + if (ex.RetryAfterMs is null) throw ex!; //we have a ratelimit error await Task.Delay(ex.RetryAfterMs.Value, cancellationToken); request.ResetSendStatus(); @@ -209,7 +218,7 @@ public class MatrixHttpClient { } // GetStreamAsync - public new async Task<Stream> GetStreamAsync(string requestUri, CancellationToken cancellationToken = default) { + public async Task<Stream> GetStreamAsync(string requestUri, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = await SendAsync(request, cancellationToken); @@ -271,5 +280,12 @@ public class MatrixHttpClient { var request = new HttpRequestMessage(HttpMethod.Delete, url); await SendAsync(request); } + + public async Task<HttpResponseMessage> DeleteAsJsonAsync<T>(string url, T payload) { + var request = new HttpRequestMessage(HttpMethod.Delete, url) { + Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json") + }; + return await SendAsync(request); + } } #endif diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index f95d6f8..adcc714 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -8,7 +8,6 @@ using LibMatrix.Filters; using LibMatrix.Homeservers; using LibMatrix.Interfaces.Services; using LibMatrix.Responses; -using LibMatrix.Utilities; using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; @@ -16,8 +15,8 @@ namespace LibMatrix.Helpers; public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null, IStorageProvider? storageProvider = null) { private SyncFilter? _filter; private string? _namedFilterName; - private bool _filterIsDirty = false; - private string? _filterId = null; + private bool _filterIsDirty; + private string? _filterId; public string? Since { get; set; } public int Timeout { get; set; } = 30000; @@ -225,7 +224,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg 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) { + foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events ?? []) { stateEventResponse.RoomId = updatedRoom.Key; var tasks = TimelineEventHandlers.Select(x => x(stateEventResponse)).ToList(); await Task.WhenAll(tasks); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 77a72c8..c1bbc5a 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -5,8 +5,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Web; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Extensions; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers.Extensions.NamedCaches; @@ -14,7 +13,6 @@ using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; using LibMatrix.Utilities; -using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Homeservers; @@ -41,11 +39,12 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { public string UserId => WhoAmI.UserId; public string UserLocalpart => UserId.Split(":")[0][1..]; public string ServerName => UserId.Split(":", 2)[1]; + public string BaseUrl => ClientHttpClient.BaseAddress!.ToString().TrimEnd('/'); [JsonIgnore] public string AccessToken { get; set; } - public HsNamedCaches NamedCaches { get; set; } = null!; + public HsNamedCaches NamedCaches { get; set; } public GenericRoom GetRoom(string roomId) { return new GenericRoom(this, roomId); @@ -387,11 +386,11 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { /// <returns>All account data.</returns> /// <exception cref="Exception"></exception> public async Task<EventList?> EnumerateAccountData() { - var syncHelper = new SyncHelper(this); - syncHelper.FilterId = await NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetAccountData); + var syncHelper = new SyncHelper(this) { + FilterId = await NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetAccountData) + }; var resp = await syncHelper.SyncAsync(); - if (resp is null) throw new Exception("Sync failed"); - return resp.AccountData; + return resp?.AccountData ?? throw new Exception("Sync failed"); } private Dictionary<string, string>? _namedFilterCache; @@ -420,22 +419,22 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { #region Authenticated Media // TODO: implement /_matrix/client/v1/media/config when it's actually useful - https://spec.matrix.org/v1.11/client-server-api/#get_matrixclientv1mediaconfig + private bool? _serverSupportsAuthMedia; - private (string ServerName, string MediaId) ParseMxcUri(string mxcUri) { - if (!mxcUri.StartsWith("mxc://")) throw new ArgumentException("Matrix Content URIs must start with 'mxc://'", nameof(mxcUri)); - var parts = mxcUri[6..].Split('/'); - if (parts.Length != 2) throw new ArgumentException($"Invalid Matrix Content URI '{mxcUri}' passed! Matrix Content URIs must exist of only 2 parts!", nameof(mxcUri)); - return (parts[0], parts[1]); - } + public async Task<string> GetMediaUrlAsync(MxcUri mxcUri, string? filename = null, int? timeout = null) { + if (_serverSupportsAuthMedia == true) return mxcUri.ToDownloadUri(BaseUrl, filename, timeout); + if (_serverSupportsAuthMedia == false) return mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout); - public async Task<Stream> GetMediaStreamAsync(string mxcUri, string? filename = null, int? timeout = null) { - var (serverName, mediaId) = ParseMxcUri(mxcUri); try { - var uri = $"/_matrix/client/v1/media/download/{serverName}/{mediaId}"; - if (!string.IsNullOrWhiteSpace(filename)) uri += $"/{HttpUtility.UrlEncode(filename)}"; - if (timeout is not null) uri += $"?timeout_ms={timeout}"; - var res = await ClientHttpClient.GetAsync(uri); - return await res.Content.ReadAsStreamAsync(); + // Console.WriteLine($"Trying authenticated media URL: {uri}"); + var res = await ClientHttpClient.SendAsync(new() { + Method = HttpMethod.Head, + RequestUri = (new Uri(mxcUri.ToDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute)) + }); + if (res.IsSuccessStatusCode) { + _serverSupportsAuthMedia = true; + return mxcUri.ToDownloadUri(BaseUrl, filename, timeout); + } } catch (MatrixException e) { if (e is not { ErrorCode: "M_UNKNOWN" }) throw; @@ -443,11 +442,15 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { //fallback to legacy media try { - var uri = $"/_matrix/media/v1/download/{serverName}/{mediaId}"; - if (!string.IsNullOrWhiteSpace(filename)) uri += $"/{HttpUtility.UrlEncode(filename)}"; - if (timeout is not null) uri += $"?timeout_ms={timeout}"; - var res = await ClientHttpClient.GetAsync(uri); - return await res.Content.ReadAsStreamAsync(); + // Console.WriteLine($"Trying legacy media URL: {uri}"); + var res = await ClientHttpClient.SendAsync(new() { + Method = HttpMethod.Head, + RequestUri = new(mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute) + }); + if (res.IsSuccessStatusCode) { + _serverSupportsAuthMedia = false; + return mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout); + } } catch (MatrixException e) { if (e is not { ErrorCode: "M_UNKNOWN" }) throw; @@ -455,19 +458,25 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { throw new LibMatrixException() { ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED, - Error = "Failed to download media" + Error = "Failed to get media URL" }; - // return default; } - public async Task<Stream> GetThumbnailStreamAsync(string mxcUri, int width, int height, string? method = null, int? timeout = null) { - var (serverName, mediaId) = ParseMxcUri(mxcUri); + public async Task<Stream> GetMediaStreamAsync(string mxcUri, string? filename = null, int? timeout = null) { + var uri = await GetMediaUrlAsync(mxcUri, filename, timeout); + var res = await ClientHttpClient.GetAsync(uri); + return await res.Content.ReadAsStreamAsync(); + } + + public async Task<Stream> GetThumbnailStreamAsync(MxcUri mxcUri, int width, int height, string? method = null, int? timeout = null) { + var (serverName, mediaId) = mxcUri; + try { var uri = new Uri($"/_matrix/client/v1/thumbnail/{serverName}/{mediaId}"); uri = uri.AddQuery("width", width.ToString()); uri = uri.AddQuery("height", height.ToString()); if (!string.IsNullOrWhiteSpace(method)) uri = uri.AddQuery("method", method); - if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()); + if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()!); var res = await ClientHttpClient.GetAsync(uri.ToString()); return await res.Content.ReadAsStreamAsync(); @@ -478,11 +487,11 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { //fallback to legacy media try { - var uri = new Uri($"/_matrix/media/v1/thumbnail/{serverName}/{mediaId}"); + var uri = new Uri($"/_matrix/media/v3/thumbnail/{serverName}/{mediaId}"); uri = uri.AddQuery("width", width.ToString()); uri = uri.AddQuery("height", height.ToString()); if (!string.IsNullOrWhiteSpace(method)) uri = uri.AddQuery("method", method); - if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()); + if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()!); var res = await ClientHttpClient.GetAsync(uri.ToString()); return await res.Content.ReadAsStreamAsync(); @@ -509,7 +518,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { //fallback to legacy media try { - var res = await ClientHttpClient.GetAsync($"/_matrix/media/v1/preview_url?url={HttpUtility.UrlEncode(url)}"); + var res = await ClientHttpClient.GetAsync($"/_matrix/media/v3/preview_url?url={HttpUtility.UrlEncode(url)}"); return await res.Content.ReadFromJsonAsync<Dictionary<string, JsonValue>>(); } catch (MatrixException e) { diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverHSE.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverHSE.cs new file mode 100644
index 0000000..1cc8ca2 --- /dev/null +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverHSE.cs
@@ -0,0 +1,16 @@ +using LibMatrix.Homeservers.ImplementationDetails.Synapse; +using LibMatrix.Responses; +using LibMatrix.Services; + +namespace LibMatrix.Homeservers; + +public class AuthenticatedHomeserverHSE : AuthenticatedHomeserverGeneric { + public AuthenticatedHomeserverHSE(string serverName, HomeserverResolverService.WellKnownUris wellKnownUris, string? proxy, string accessToken) : base(serverName, + wellKnownUris, proxy, accessToken) { } + + public Task<Dictionary<string, LoginResponse>> GetExternalProfilesAsync() => + ClientHttpClient.GetFromJsonAsync<Dictionary<string, LoginResponse>>("/_hse/client/v1/external_profiles"); + + public Task SetExternalProfile(string sessionName, LoginResponse session) => + ClientHttpClient.PutAsJsonAsync($"/_hse/client/v1/external_profiles/{sessionName}", session); +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
index 9acdd58..1c0a656 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs
@@ -1,5 +1,3 @@ -using ArcaneLibs.Extensions; -using LibMatrix.Filters; using LibMatrix.Homeservers.ImplementationDetails.Synapse; using LibMatrix.Services; diff --git a/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs
index 622eef6..9f11fa0 100644 --- a/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs +++ b/LibMatrix/Homeservers/Extensions/NamedCaches/NamedCache.cs
@@ -3,35 +3,65 @@ namespace LibMatrix.Homeservers.Extensions.NamedCaches; public class NamedCache<T>(AuthenticatedHomeserverGeneric hs, string name) where T : class { private Dictionary<string, T>? _cache = new(); private DateTime _expiry = DateTime.MinValue; - + private SemaphoreSlim _lock = new(1, 1); + + public TimeSpan ExpiryTime { get; set; } = TimeSpan.FromMinutes(5); + public DateTime GetCurrentExpiryTime() => _expiry; + + /// <summary> + /// Update the cached map with the latest data from the homeserver. + /// </summary> + /// <returns>The updated data</returns> public async Task<Dictionary<string, T>> ReadCacheMapAsync() { - _cache = await hs.GetAccountDataOrNullAsync<Dictionary<string, T>>(name); + _cache = await hs.GetAccountDataAsync<Dictionary<string, T>>(name); return _cache ?? new(); } - - public async Task<Dictionary<string,T>> ReadCacheMapCachedAsync() { + + public async Task<Dictionary<string, T>> ReadCacheMapCachedAsync() { + await _lock.WaitAsync(); if (_expiry < DateTime.Now || _cache == null) { _cache = await ReadCacheMapAsync(); - _expiry = DateTime.Now.AddMinutes(5); + _expiry = DateTime.Now.Add(ExpiryTime); } + _lock.Release(); + return _cache; } - - public virtual async Task<T?> GetValueAsync(string key) { - return (await ReadCacheMapCachedAsync()).GetValueOrDefault(key); + + public virtual async Task<T?> GetValueAsync(string key, bool useCache = true) { + return (await (useCache ? ReadCacheMapCachedAsync() : ReadCacheMapAsync())).GetValueOrDefault(key); } - - public virtual async Task<T> SetValueAsync(string key, T value) { - var cache = await ReadCacheMapCachedAsync(); + + public virtual async Task<T> SetValueAsync(string key, T value, bool unsafeUseCache = false) { + if (!unsafeUseCache) + await _lock.WaitAsync(); + var cache = await (unsafeUseCache ? ReadCacheMapCachedAsync() : ReadCacheMapAsync()); cache[key] = value; await hs.SetAccountDataAsync(name, cache); + if (!unsafeUseCache) + _lock.Release(); + return value; } - - public virtual async Task<T> GetOrSetValueAsync(string key, Func<Task<T>> value) { - return (await ReadCacheMapCachedAsync()).GetValueOrDefault(key) ?? await SetValueAsync(key, await value()); + + public virtual async Task<T> RemoveValueAsync(string key, bool unsafeUseCache = false) { + if (!unsafeUseCache) + await _lock.WaitAsync(); + var cache = await (unsafeUseCache ? ReadCacheMapCachedAsync() : ReadCacheMapAsync()); + var removedValue = cache[key]; + cache.Remove(key); + await hs.SetAccountDataAsync(name, cache); + + if (!unsafeUseCache) + _lock.Release(); + + return removedValue; + } + + public virtual async Task<T> GetOrSetValueAsync(string key, Func<Task<T>> value, bool unsafeUseCache = false) { + return (await (unsafeUseCache ? ReadCacheMapCachedAsync() : ReadCacheMapAsync())).GetValueOrDefault(key) ?? await SetValueAsync(key, await value()); } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/FederationClient.cs b/LibMatrix/Homeservers/FederationClient.cs
index 22653e4..617b737 100644 --- a/LibMatrix/Homeservers/FederationClient.cs +++ b/LibMatrix/Homeservers/FederationClient.cs
@@ -1,7 +1,6 @@ using System.Text.Json.Serialization; using LibMatrix.Extensions; using LibMatrix.Services; -using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Homeservers; @@ -14,8 +13,8 @@ public class FederationClient { if (proxy is not null) HttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", federationEndpoint); } - public MatrixHttpClient HttpClient { get; set; } = null!; - public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } = null!; + public MatrixHttpClient HttpClient { get; set; } + public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } public async Task<ServerVersionResponse> GetServerVersionAsync() => await HttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version"); } diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/SynapseAdminRoomDeleteRequest.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/SynapseAdminRoomDeleteRequest.cs
index 67a3104..aee2a7e 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/SynapseAdminRoomDeleteRequest.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/SynapseAdminRoomDeleteRequest.cs
@@ -20,4 +20,35 @@ public class SynapseAdminRoomDeleteRequest { [JsonPropertyName("force_purge")] public bool ForcePurge { get; set; } +} + +public class SynapseAdminRoomDeleteResponse { + [JsonPropertyName("delete_id")] + public string DeleteId { get; set; } = null!; +} + +public class SynapseAdminRoomDeleteStatusList { + [JsonPropertyName("results")] + public List<SynapseAdminRoomDeleteStatus> Results { get; set; } +} +public class SynapseAdminRoomDeleteStatus { + [JsonPropertyName("status")] + public string Status { get; set; } = null!; + + [JsonPropertyName("shutdown_room")] + public RoomShutdownInfo ShutdownRoom { get; set; } + + public class RoomShutdownInfo { + [JsonPropertyName("kicked_users")] + public List<string>? KickedUsers { get; set; } + + [JsonPropertyName("failed_to_kick_users")] + public List<string>? FailedToKickUsers { get; set; } + + [JsonPropertyName("local_aliases")] + public List<string>? LocalAliasses { get; set; } + + [JsonPropertyName("new_room_id")] + public string? NewRoomId { get; set; } + } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomMemberListResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomMemberListResult.cs new file mode 100644
index 0000000..cb2ec08 --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminRoomMemberListResult.cs
@@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; + +public class SynapseAdminRoomMemberListResult { + [JsonPropertyName("members")] + public List<string> Members { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminUserRedactIdResponse.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminUserRedactIdResponse.cs new file mode 100644
index 0000000..3f5f865 --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminUserRedactIdResponse.cs
@@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; + +public class SynapseAdminUserRedactIdResponse { + [JsonPropertyName("redact_id")] + public string RedactionId { get; set; } +} + +public class SynapseAdminRedactStatusResponse { + /// <summary> + /// One of "scheduled", "active", "completed", "failed" + /// </summary> + [JsonPropertyName("status")] + public string Status { get; set; } + + /// <summary> + /// Key: Event ID, Value: Error message + /// </summary> + [JsonPropertyName("failed_redactions")] + public Dictionary<string, string> FailedRedactions { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseUserMediaResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseUserMediaResult.cs new file mode 100644
index 0000000..5530cc3 --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseUserMediaResult.cs
@@ -0,0 +1,40 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; + +public class SynapseAdminUserMediaResult { + [JsonPropertyName("total")] + public int Total { get; set; } + + [JsonPropertyName("next_token")] + public string? NextToken { get; set; } + + [JsonPropertyName("media")] + public List<MediaInfo> Media { get; set; } = new(); + + public class MediaInfo { + [JsonPropertyName("created_ts")] + public long CreatedTimestamp { get; set; } + + [JsonPropertyName("last_access_ts")] + public long? LastAccessTimestamp { get; set; } + + [JsonPropertyName("media_id")] + public string MediaId { get; set; } + + [JsonPropertyName("media_length")] + public int MediaLength { get; set; } + + [JsonPropertyName("media_type")] + public string MediaType { get; set; } + + [JsonPropertyName("quarantined_by")] + public string? QuarantinedBy { get; set; } + + [JsonPropertyName("safe_from_quarantine")] + public bool SafeFromQuarantine { get; set; } + + [JsonPropertyName("upload_name")] + public string UploadName { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
index 4d8a577..a48402a 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs
@@ -1,8 +1,17 @@ +// #define LOG_SKIP + +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; +using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; +using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests; +using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; +using LibMatrix.Responses; using LibMatrix.Filters; using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; @@ -11,6 +20,7 @@ using LibMatrix.Responses; namespace LibMatrix.Homeservers.ImplementationDetails.Synapse; public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedHomeserver) { + private SynapseAdminUserCleanupExecutor UserCleanupExecutor { get; } = new(authenticatedHomeserver); // https://github.com/element-hq/synapse/tree/develop/docs/admin_api // https://github.com/element-hq/synapse/tree/develop/docs/usage/administration/admin_api @@ -31,71 +41,123 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomListResult>(url); totalRooms ??= res.TotalRooms; - Console.WriteLine(res.ToJson(false)); + // Console.WriteLine(res.ToJson(false)); foreach (var room in res.Rooms) { if (localFilter is not null) { - if (!room.RoomId.Contains(localFilter.RoomIdContains)) { + if (!string.IsNullOrWhiteSpace(localFilter.RoomIdContains) && !room.RoomId.Contains(localFilter.RoomIdContains, StringComparison.OrdinalIgnoreCase)) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule roomid."); +#endif continue; } - if (!room.Name?.Contains(localFilter.NameContains) == true) { + if (!string.IsNullOrWhiteSpace(localFilter.NameContains) && room.Name?.Contains(localFilter.NameContains, StringComparison.OrdinalIgnoreCase) != true) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule roomname."); +#endif continue; } - if (!room.CanonicalAlias?.Contains(localFilter.CanonicalAliasContains) == true) { + if (!string.IsNullOrWhiteSpace(localFilter.CanonicalAliasContains) && + room.CanonicalAlias?.Contains(localFilter.CanonicalAliasContains, StringComparison.OrdinalIgnoreCase) != true) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule alias."); +#endif continue; } - if (!room.Version.Contains(localFilter.VersionContains)) { + if (!string.IsNullOrWhiteSpace(localFilter.VersionContains) && !room.Version.Contains(localFilter.VersionContains, StringComparison.OrdinalIgnoreCase)) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule version."); +#endif continue; } - if (!room.Creator.Contains(localFilter.CreatorContains)) { + if (!string.IsNullOrWhiteSpace(localFilter.CreatorContains) && !room.Creator.Contains(localFilter.CreatorContains, StringComparison.OrdinalIgnoreCase)) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule creator."); +#endif continue; } - if (!room.Encryption?.Contains(localFilter.EncryptionContains) == true) { + if (!string.IsNullOrWhiteSpace(localFilter.EncryptionContains) && + room.Encryption?.Contains(localFilter.EncryptionContains, StringComparison.OrdinalIgnoreCase) != true) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule encryption."); +#endif continue; } - if (!room.JoinRules?.Contains(localFilter.JoinRulesContains) == true) { + if (!string.IsNullOrWhiteSpace(localFilter.JoinRulesContains) && + room.JoinRules?.Contains(localFilter.JoinRulesContains, StringComparison.OrdinalIgnoreCase) != true) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joinrules."); +#endif continue; } - if (!room.GuestAccess?.Contains(localFilter.GuestAccessContains) == true) { + if (!string.IsNullOrWhiteSpace(localFilter.GuestAccessContains) && + room.GuestAccess?.Contains(localFilter.GuestAccessContains, StringComparison.OrdinalIgnoreCase) != true) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule guestaccess."); +#endif continue; } - if (!room.HistoryVisibility?.Contains(localFilter.HistoryVisibilityContains) == true) { + if (!string.IsNullOrWhiteSpace(localFilter.HistoryVisibilityContains) && + room.HistoryVisibility?.Contains(localFilter.HistoryVisibilityContains, StringComparison.OrdinalIgnoreCase) != true) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule history visibility."); +#endif continue; } if (localFilter.CheckFederation && room.Federatable != localFilter.Federatable) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule federation."); +#endif continue; } if (localFilter.CheckPublic && room.Public != localFilter.Public) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule public."); +#endif continue; } + if (room.StateEvents < localFilter.StateEventsGreaterThan || room.StateEvents > localFilter.StateEventsLessThan) { + totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joined local members."); +#endif + continue; + } + if (room.JoinedMembers < localFilter.JoinedMembersGreaterThan || room.JoinedMembers > localFilter.JoinedMembersLessThan) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joined members: {localFilter.JoinedMembersGreaterThan} < {room.JoinedLocalMembers} < {localFilter.JoinedMembersLessThan}."); +#endif continue; } if (room.JoinedLocalMembers < localFilter.JoinedLocalMembersGreaterThan || room.JoinedLocalMembers > localFilter.JoinedLocalMembersLessThan) { totalRooms--; +#if LOG_SKIP + Console.WriteLine($"Skipped room {room.ToJson(indent: false)} on rule joined local members: {localFilter.JoinedLocalMembersGreaterThan} < {room.JoinedLocalMembers} < {localFilter.JoinedLocalMembersLessThan}."); +#endif continue; } } @@ -347,7 +409,7 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH #region Media public async Task<SynapseAdminRoomMediaListResult> GetRoomMediaAsync(string roomId) { - var url = new Uri($"/_synapse/admin/v1/rooms/{roomId.UrlEncode()}/media", UriKind.Relative); + var url = new Uri($"/_synapse/admin/v1/room/{roomId.UrlEncode()}/media", UriKind.Relative); return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomMediaListResult>(url.ToString()); } @@ -355,4 +417,127 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH // public async IAsyncEnumerable<SynapseAdminRoomMediaListResult> #endregion + + public async Task<SynapseAdminUserRedactIdResponse?> DeleteAllMessages(string mxid, List<string>? rooms = null, string? reason = null, int? limit = 100000, + bool waitForCompletion = true) { + rooms ??= []; + + Dictionary<string, object> payload = new(); + if (rooms.Count > 0) payload["rooms"] = rooms; + if (!string.IsNullOrEmpty(reason)) payload["reason"] = reason; + if (limit.HasValue) payload["limit"] = limit.Value; + + var redactIdResp = await authenticatedHomeserver.ClientHttpClient.PostAsJsonAsync($"/_synapse/admin/v1/user/{mxid}/redact", payload); + var redactId = await redactIdResp.Content.ReadFromJsonAsync<SynapseAdminUserRedactIdResponse>(); + + if (waitForCompletion) { + while (true) { + var status = await GetRedactStatus(redactId!.RedactionId); + if (status?.Status != "pending") break; + await Task.Delay(1000); + } + } + + return redactId; + } + + public async Task<SynapseAdminRedactStatusResponse?> GetRedactStatus(string redactId) { + return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRedactStatusResponse>( + $"/_synapse/admin/v1/user/redact_status/{redactId}"); + } + + public async Task DeactivateUserAsync(string mxid, bool erase = false, bool eraseMessages = false, bool extraCleanup = false) { + if (eraseMessages) { + await DeleteAllMessages(mxid); + } + + if (extraCleanup) { + await UserCleanupExecutor.CleanupUser(mxid); + } + + await authenticatedHomeserver.ClientHttpClient.PostAsJsonAsync($"/_synapse/admin/v1/deactivate", new { erase }); + } + + public async Task ResetPasswordAsync(string mxid, string newPassword, bool logoutDevices = false) { + await authenticatedHomeserver.ClientHttpClient.PostAsJsonAsync($"/_synapse/admin/v1/reset_password/{mxid}", + new { new_password = newPassword, logout_devices = logoutDevices }); + } + + public async Task<SynapseAdminUserMediaResult> GetUserMediaAsync(string mxid, int? limit = 100, string? from = null, string? orderBy = null, string? dir = null) { + var url = $"/_synapse/admin/v1/users/{mxid}/media"; + if (limit.HasValue) url += $"?limit={limit}"; + if (!string.IsNullOrEmpty(from)) url += $"&from={from}"; + if (!string.IsNullOrEmpty(orderBy)) url += $"&order_by={orderBy}"; + if (!string.IsNullOrEmpty(dir)) url += $"&dir={dir}"; + return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminUserMediaResult>(url); + } + + public async IAsyncEnumerable<SynapseAdminUserMediaResult.MediaInfo> GetUserMediaEnumerableAsync(string mxid, int chunkSize = 100, string? orderBy = null, string? dir = null) { + SynapseAdminUserMediaResult? res = null; + do { + res = await GetUserMediaAsync(mxid, chunkSize, res?.NextToken, orderBy, dir); + foreach (var media in res.Media) { + yield return media; + } + } while (!string.IsNullOrEmpty(res.NextToken)); + } + + public async Task BlockRoom(string roomId, bool block = true) { + await authenticatedHomeserver.ClientHttpClient.PutAsJsonAsync($"/_synapse/admin/v1/rooms/{roomId}/block", new { + block + }); + } + + public async Task<SynapseAdminRoomDeleteResponse> DeleteRoom(string roomId, SynapseAdminRoomDeleteRequest request, bool waitForCompletion = true) { + var resp = await authenticatedHomeserver.ClientHttpClient.DeleteAsJsonAsync($"/_synapse/admin/v2/rooms/{roomId}", request); + var deleteResp = await resp.Content.ReadFromJsonAsync<SynapseAdminRoomDeleteResponse>(); + + if (waitForCompletion) { + while (true) { + var status = await GetRoomDeleteStatus(deleteResp!.DeleteId); + if (status?.Status != "pending") break; + await Task.Delay(1000); + } + } + + return deleteResp!; + } + + public async Task<SynapseAdminRoomDeleteStatus> GetRoomDeleteStatusByRoomId(string roomId) { + return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomDeleteStatus>( + $"/_synapse/admin/v2/rooms/{roomId}/delete_status"); + } + + public async Task<SynapseAdminRoomDeleteStatus> GetRoomDeleteStatus(string deleteId) { + return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomDeleteStatus>( + $"/_synapse/admin/v2/rooms/delete_status/{deleteId}"); + } + + public async Task<SynapseAdminRoomMemberListResult> GetRoomMembersAsync(string roomId) { + return await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync<SynapseAdminRoomMemberListResult>($"/_synapse/admin/v1/rooms/{roomId}/members"); + } + + public async Task QuarantineMediaByRoomId(string roomId) { + await authenticatedHomeserver.ClientHttpClient.PutAsJsonAsync($"/_synapse/admin/v1/room/{roomId}/media/quarantine", new { }); + } + + public async Task QuarantineMediaByUserId(string mxid) { + await authenticatedHomeserver.ClientHttpClient.PutAsJsonAsync($"/_synapse/admin/v1/user/{mxid}/media/quarantine", new { }); + } + + public async Task QuarantineMediaById(string serverName, string mediaId) { + await authenticatedHomeserver.ClientHttpClient.PutAsJsonAsync($"/_synapse/admin/v1/media/quarantine/{serverName}/{mediaId}", new { }); + } + + public async Task QuarantineMediaById(MxcUri mxcUri) { + await authenticatedHomeserver.ClientHttpClient.PutAsJsonAsync($"/_synapse/admin/v1/media/quarantine/{mxcUri.ServerName}/{mxcUri.MediaId}", new { }); + } + + public async Task DeleteMediaById(string serverName, string mediaId) { + await authenticatedHomeserver.ClientHttpClient.DeleteAsync($"/_synapse/admin/v1/media/{serverName}/{mediaId}"); + } + + public async Task DeleteMediaById(MxcUri mxcUri) { + await authenticatedHomeserver.ClientHttpClient.DeleteAsync($"/_synapse/admin/v1/media/{mxcUri.ServerName}/{mxcUri.MediaId}"); + } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminUserCleanupExecutor.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminUserCleanupExecutor.cs new file mode 100644
index 0000000..6edf40c --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminUserCleanupExecutor.cs
@@ -0,0 +1,27 @@ +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse; + +public class SynapseAdminUserCleanupExecutor(AuthenticatedHomeserverSynapse homeserver) { + /* + Remove mappings of SSO IDs + Delete media uploaded by user (included avatar images) + Delete sent and received messages + Remove the user's creation (registration) timestamp + Remove rate limit overrides + Remove from monthly active users + Remove user's consent information (consent version and timestamp) + */ + public async Task CleanupUser(string mxid) { + // change the user's password to a random one + var newPassword = Guid.NewGuid().ToString(); + await homeserver.Admin.ResetPasswordAsync(mxid, newPassword, true); + await homeserver.Admin.DeleteAllMessages(mxid); + + } + private async Task RunUserTasks(string mxid) { + var auth = await homeserver.Admin.LoginUserAsync(mxid, TimeSpan.FromDays(1)); + var userHs = new AuthenticatedHomeserverSynapse(homeserver.ServerName, homeserver.WellKnownUris, null, auth.AccessToken); + await userHs.Initialise(); + + + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index adaac6d..45ecb18 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -1,35 +1,34 @@ using System.Net.Http.Json; +using System.Text; using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Web; using ArcaneLibs.Extensions; using LibMatrix.Extensions; using LibMatrix.Responses; using LibMatrix.Services; -using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Homeservers; public class RemoteHomeserver { - public RemoteHomeserver(string baseUrl, HomeserverResolverService.WellKnownUris wellKnownUris, string? proxy) { + public RemoteHomeserver(string serverName, HomeserverResolverService.WellKnownUris wellKnownUris, string? proxy) { if (string.IsNullOrWhiteSpace(proxy)) proxy = null; - BaseUrl = baseUrl; + ServerNameOrUrl = serverName; WellKnownUris = wellKnownUris; ClientHttpClient = new MatrixHttpClient { - BaseAddress = new Uri(proxy?.TrimEnd('/') ?? wellKnownUris.Client?.TrimEnd('/') ?? throw new InvalidOperationException($"No client URI for {baseUrl}!")), + BaseAddress = new Uri(proxy?.TrimEnd('/') ?? wellKnownUris.Client?.TrimEnd('/') ?? throw new InvalidOperationException($"No client URI for {serverName}!")), // Timeout = TimeSpan.FromSeconds(300) // TODO: Re-implement this }; - if (proxy is not null) ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl); + if (proxy is not null) ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName); if (!string.IsNullOrWhiteSpace(wellKnownUris.Server)) FederationClient = new FederationClient(WellKnownUris.Server!, proxy); Auth = new(this); } private Dictionary<string, object> _profileCache { get; set; } = new(); - public string BaseUrl { get; } + public string ServerNameOrUrl { get; } [JsonIgnore] public MatrixHttpClient ClientHttpClient { get; set; } @@ -51,7 +50,7 @@ public class RemoteHomeserver { var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/profile/{HttpUtility.UrlEncode(mxid)}"); var data = await resp.Content.ReadFromJsonAsync<UserProfileResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data); - _profileCache[mxid] = data; + _profileCache[mxid] = data ?? throw new InvalidOperationException($"Could not get profile for {mxid}"); return data; } @@ -62,7 +61,7 @@ public class RemoteHomeserver { var resp = await ClientHttpClient.GetAsync($"/_matrix/client/versions"); var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data); - return data; + return data ?? throw new InvalidOperationException("ClientVersionsResponse is null"); } public async Task<AliasResult> ResolveRoomAliasAsync(string alias) { @@ -70,7 +69,16 @@ public class RemoteHomeserver { var data = await resp.Content.ReadFromJsonAsync<AliasResult>(); //var text = await resp.Content.ReadAsStringAsync(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson()); - return data; + return data ?? throw new InvalidOperationException($"Could not resolve alias {alias}"); + } + + public Task<PublicRoomDirectoryResult> GetPublicRoomsAsync(int limit = 100, string? server = null, string? since = null) => + ClientHttpClient.GetFromJsonAsync<PublicRoomDirectoryResult>(buildUriWithParams("/_matrix/client/v3/publicRooms", (nameof(limit), true, limit), + (nameof(server), !string.IsNullOrWhiteSpace(server), server), (nameof(since), !string.IsNullOrWhiteSpace(since), since))); + + // TODO: move this somewhere else + private string buildUriWithParams(string url, params (string name, bool include, object? value)[] values) { + return url + "?" + string.Join("&", values.Where(x => x.include)); } #region Authentication @@ -82,12 +90,11 @@ public class RemoteHomeserver { type = "m.id.user", user = username }, - password = password, + password, initial_device_display_name = deviceName }); var data = await resp.Content.ReadFromJsonAsync<LoginResponse>(); - if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data.ToJson()); - return data; + return data ?? throw new InvalidOperationException("LoginResponse is null"); } public async Task<LoginResponse> RegisterAsync(string username, string password, string? deviceName = null) { @@ -103,26 +110,64 @@ public class RemoteHomeserver { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); var data = await resp.Content.ReadFromJsonAsync<LoginResponse>(); - if (!resp.IsSuccessStatusCode) Console.WriteLine("Register: " + data.ToJson()); - return data; + return data ?? throw new InvalidOperationException("LoginResponse is null"); } #endregion [Obsolete("This call uses the deprecated unauthenticated media endpoints, please switch to the relevant AuthenticatedHomeserver methods instead.", true)] - public string? ResolveMediaUri(string? mxcUri) { - if (mxcUri is null) return null; - if (mxcUri.StartsWith("https://")) return mxcUri; - return $"{ClientHttpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//_matrix", "/_matrix"); - } + public virtual string? ResolveMediaUri(string? mxcUri) => null; public UserInteractiveAuthClient Auth; } +public class PublicRoomDirectoryResult { + [JsonPropertyName("chunk")] + public List<PublicRoomListItem> Chunk { get; set; } + + [JsonPropertyName("next_batch")] + public string? NextBatch { get; set; } + + [JsonPropertyName("prev_batch")] + public string? PrevBatch { get; set; } + + [JsonPropertyName("total_room_count_estimate")] + public int TotalRoomCountEstimate { get; set; } + + public class PublicRoomListItem { + [JsonPropertyName("avatar_url")] + public string? AvatarUrl { get; set; } + + [JsonPropertyName("canonical_alias")] + public string? CanonicalAlias { get; set; } + + [JsonPropertyName("guest_can_join")] + public bool GuestCanJoin { get; set; } + + [JsonPropertyName("join_rule")] + public string JoinRule { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("num_joined_members")] + public int NumJoinedMembers { get; set; } + + [JsonPropertyName("room_id")] + public string RoomId { get; set; } + + [JsonPropertyName("topic")] + public string? Topic { get; set; } + + [JsonPropertyName("world_readable")] + public bool WorldReadable { get; set; } + } +} + public class AliasResult { [JsonPropertyName("room_id")] - public string RoomId { get; set; } = null!; + public string RoomId { get; set; } [JsonPropertyName("servers")] - public List<string> Servers { get; set; } = null!; + public List<string> Servers { get; set; } } \ No newline at end of file diff --git a/LibMatrix/Homeservers/UserInteractiveAuthClient.cs b/LibMatrix/Homeservers/UserInteractiveAuthClient.cs
index 8be2cb9..8de01d2 100644 --- a/LibMatrix/Homeservers/UserInteractiveAuthClient.cs +++ b/LibMatrix/Homeservers/UserInteractiveAuthClient.cs
@@ -1,7 +1,5 @@ -using System.Net.Http.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; -using ArcaneLibs.Extensions; using LibMatrix.Responses; namespace LibMatrix.Homeservers; @@ -51,27 +49,27 @@ public class UserInteractiveAuthClient { internal class RegisterFlowsResponse { [JsonPropertyName("session")] - public string Session { get; set; } = null!; + public string Session { get; set; } [JsonPropertyName("flows")] - public List<RegisterFlow> Flows { get; set; } = null!; + public List<RegisterFlow> Flows { get; set; } [JsonPropertyName("params")] - public JsonObject Params { get; set; } = null!; + public JsonObject Params { get; set; } public class RegisterFlow { [JsonPropertyName("stages")] - public List<string> Stages { get; set; } = null!; + public List<string> Stages { get; set; } } } internal class LoginFlowsResponse { [JsonPropertyName("flows")] - public List<LoginFlow> Flows { get; set; } = null!; + public List<LoginFlow> Flows { get; set; } public class LoginFlow { [JsonPropertyName("type")] - public string Type { get; set; } = null!; + public string Type { get; set; } } } diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index b992ad6..042d943 100644 --- a/LibMatrix/LibMatrix.csproj +++ b/LibMatrix/LibMatrix.csproj
@@ -12,13 +12,13 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0"/> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0"/> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" /> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" /> <ProjectReference Include="..\LibMatrix.EventTypes\LibMatrix.EventTypes.csproj"/> </ItemGroup> <ItemGroup> - <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20241122-053825" Condition="'$(Configuration)' == 'Release'"/> + <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250307-202359" Condition="'$(Configuration)' == 'Release'" /> <ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/> </ItemGroup> diff --git a/LibMatrix/LibMatrixException.cs b/LibMatrix/LibMatrixException.cs
index 5854826..27cfc2a 100644 --- a/LibMatrix/LibMatrixException.cs +++ b/LibMatrix/LibMatrixException.cs
@@ -1,5 +1,7 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; +// ReSharper disable MemberCanBePrivate.Global namespace LibMatrix; @@ -20,6 +22,7 @@ public class LibMatrixException : Exception { _ => $"Unknown error: {GetAsObject().ToJson(ignoreNull: true)}" }}\nError: {Error}"; + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Follows spec naming")] public static class ErrorCodes { public const string M_NOT_FOUND = "M_NOT_FOUND"; public const string M_UNSUPPORTED = "M_UNSUPPORTED"; diff --git a/LibMatrix/MatrixException.cs b/LibMatrix/MatrixException.cs
index afdeefe..519f99e 100644 --- a/LibMatrix/MatrixException.cs +++ b/LibMatrix/MatrixException.cs
@@ -1,5 +1,7 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; +// ReSharper disable MemberCanBePrivate.Global namespace LibMatrix; @@ -17,7 +19,7 @@ public class MatrixException : Exception { [JsonPropertyName("retry_after_ms")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? RetryAfterMs { get; set; } - + public string RawContent { get; set; } public object GetAsObject() => new { errcode = ErrorCode, error = Error, soft_logout = SoftLogout, retry_after_ms = RetryAfterMs }; @@ -66,6 +68,7 @@ public class MatrixException : Exception { _ => $"Unknown error: {new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true)}" }); + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Follows spec naming")] public static class ErrorCodes { public const string M_FORBIDDEN = "M_FORBIDDEN"; public const string M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"; diff --git a/LibMatrix/MxcUri.cs b/LibMatrix/MxcUri.cs new file mode 100644
index 0000000..875ae53 --- /dev/null +++ b/LibMatrix/MxcUri.cs
@@ -0,0 +1,43 @@ +using System.Diagnostics.CodeAnalysis; + +namespace LibMatrix; + +public class MxcUri { + public required string ServerName { get; set; } + public required string MediaId { get; set; } + + public static MxcUri Parse([StringSyntax("Uri")] string mxcUri) { + if (!mxcUri.StartsWith("mxc://")) throw new ArgumentException("Matrix Content URIs must start with 'mxc://'", nameof(mxcUri)); + var parts = mxcUri[6..].Split('/'); + if (parts.Length != 2) throw new ArgumentException($"Invalid Matrix Content URI '{mxcUri}' passed! Matrix Content URIs must exist of only 2 parts!", nameof(mxcUri)); + return new MxcUri { + ServerName = parts[0], + MediaId = parts[1] + }; + } + + public static implicit operator MxcUri(string mxcUri) => Parse(mxcUri); + public static implicit operator string(MxcUri mxcUri) => $"mxc://{mxcUri.ServerName}/{mxcUri.MediaId}"; + public static implicit operator (string, string)(MxcUri mxcUri) => (mxcUri.ServerName, mxcUri.MediaId); + public static implicit operator MxcUri((string serverName, string mediaId) mxcUri) => (mxcUri.serverName, mxcUri.mediaId); + // public override string ToString() => $"mxc://{ServerName}/{MediaId}"; + + public string ToDownloadUri(string? baseUrl = null, string? filename = null, int? timeout = null) { + var uri = $"{baseUrl}/_matrix/client/v1/media/download/{ServerName}/{MediaId}"; + if (filename is not null) uri += $"/{filename}"; + if (timeout is not null) uri += $"?timeout={timeout}"; + return uri; + } + + public string ToLegacyDownloadUri(string? baseUrl = null, string? filename = null, int? timeout = null) { + var uri = $"{baseUrl}/_matrix/media/v3/download/{ServerName}/{MediaId}"; + if (filename is not null) uri += $"/{filename}"; + if (timeout is not null) uri += $"?timeout_ms={timeout}"; + return uri; + } + + public void Deconstruct(out string serverName, out string mediaId) { + serverName = ServerName; + mediaId = MediaId; + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 6f47183..d9a6acd 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using LibMatrix.EventTypes; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; namespace LibMatrix.Responses; @@ -26,7 +26,7 @@ public class CreateRoomRequest { //we dont want to use this, we want more control // [JsonPropertyName("preset")] - // public string Preset { get; set; } = null!; + // public string Preset { get; set; } [JsonPropertyName("initial_state")] public List<StateEvent>? InitialState { get; set; } @@ -35,10 +35,11 @@ public class CreateRoomRequest { /// One of: ["public", "private"] /// </summary> [JsonPropertyName("visibility")] + // ReSharper disable once UnusedAutoPropertyAccessor.Global public string? Visibility { get; set; } [JsonPropertyName("power_level_content_override")] - public RoomPowerLevelEventContent? PowerLevelContentOverride { get; set; } = null!; + public RoomPowerLevelEventContent? PowerLevelContentOverride { get; set; } [JsonPropertyName("creation_content")] public JsonObject CreationContent { get; set; } = new(); @@ -50,11 +51,11 @@ public class CreateRoomRequest { /// For use only when you can't use the CreationContent property /// </summary> - public StateEvent this[string eventType, string eventKey = ""] { + public StateEvent? this[string eventType, string eventKey = ""] { get { - var stateEvent = InitialState.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey); + var stateEvent = InitialState?.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey); if (stateEvent == null) - InitialState.Add(stateEvent = new StateEvent { + InitialState?.Add(stateEvent = new StateEvent { Type = eventType, StateKey = eventKey, TypedContent = (EventContent)Activator.CreateInstance( @@ -77,7 +78,7 @@ public class CreateRoomRequest { public Dictionary<string, string> Validate() { Dictionary<string, string> errors = new(); - if (!Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$")) + if (!string.IsNullOrWhiteSpace(RoomAliasName) && !Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$")) errors.Add("room_alias_name", "Room alias name must only contain letters, numbers, underscores, and hyphens."); @@ -97,7 +98,7 @@ public class CreateRoomRequest { Invite = 25, StateDefault = 10, Redact = 50, - NotificationsPl = new RoomPowerLevelEventContent.NotificationsPL { + NotificationsPl = new RoomPowerLevelEventContent.NotificationsPowerLevels { Room = 10 }, Events = new Dictionary<string, long> { @@ -137,7 +138,7 @@ public class CreateRoomRequest { Invite = 25, StateDefault = 10, Redact = 50, - NotificationsPl = new RoomPowerLevelEventContent.NotificationsPL { + NotificationsPl = new RoomPowerLevelEventContent.NotificationsPowerLevels { Room = 10 }, Events = new Dictionary<string, long> { diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 28fb245..2f78932 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs
@@ -1,29 +1,28 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using LibMatrix.Homeservers; namespace LibMatrix.Responses; public class LoginResponse { [JsonPropertyName("access_token")] - public string AccessToken { get; set; } = null!; + public string AccessToken { get; set; } [JsonPropertyName("device_id")] - public string DeviceId { get; set; } = null!; - - private string? _homeserver; + public string DeviceId { get; set; } [JsonPropertyName("home_server")] + [field: AllowNull, MaybeNull] public string Homeserver { - get => _homeserver ?? UserId.Split(':', 2).Last(); - protected init => _homeserver = value; + get => field ?? UserId.Split(':', 2).Last(); + set; } [JsonPropertyName("user_id")] - public string UserId { get; set; } = null!; + public string UserId { get; set; } // public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) { - // var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); - // await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(Homeserver, AccessToken, proxy); + // var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); + // await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(Homeserver, AccessToken, proxy); // } } diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index a4391b7..977de3e 100644 --- a/LibMatrix/Responses/SyncResponse.cs +++ b/LibMatrix/Responses/SyncResponse.cs
@@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using LibMatrix.EventTypes.Spec.Ephemeral; using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; namespace LibMatrix.Responses; @@ -10,7 +11,7 @@ internal partial class SyncResponseSerializerContext : JsonSerializerContext { } public class SyncResponse { [JsonPropertyName("next_batch")] - public string NextBatch { get; set; } = null!; + public string NextBatch { get; set; } [JsonPropertyName("account_data")] public EventList? AccountData { get; set; } @@ -19,7 +20,7 @@ public class SyncResponse { public EventList? Presence { get; set; } [JsonPropertyName("device_one_time_keys_count")] - public Dictionary<string, int>? DeviceOneTimeKeysCount { get; set; } = null!; + public Dictionary<string, int>? DeviceOneTimeKeysCount { get; set; } [JsonPropertyName("rooms")] public RoomsDataStructure? Rooms { get; set; } diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 84a3d30..ec61a33 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -1,5 +1,4 @@ using System.Collections.Frozen; -using System.Diagnostics; using System.Net.Http.Json; using System.Security.Cryptography; using System.Text.Json; @@ -9,13 +8,10 @@ using System.Web; using ArcaneLibs.Extensions; using LibMatrix.EventTypes; using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers; -using LibMatrix.Services; -using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.RoomTypes; @@ -139,16 +135,17 @@ public class GenericRoom { return await GetStateEventAsync(type, stateKey); } catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") return default; + if (e.ErrorCode == "M_NOT_FOUND") return null; throw; } } - public async Task<MessagesResponse> GetMessagesAsync(string from = "", int? limit = null, string dir = "b", string filter = "") { + public async Task<MessagesResponse> GetMessagesAsync(string from = "", int? limit = null, string dir = "b", string? filter = "") { var url = $"/_matrix/client/v3/rooms/{RoomId}/messages?dir={dir}"; if (!string.IsNullOrWhiteSpace(from)) url += $"&from={from}"; if (limit is not null) url += $"&limit={limit}"; if (!string.IsNullOrWhiteSpace(filter)) url += $"&filter={filter}"; + var res = await Homeserver.ClientHttpClient.GetFromJsonAsync<MessagesResponse>(url); return res; } @@ -156,8 +153,8 @@ public class GenericRoom { /// <summary> /// Same as <see cref="GetMessagesAsync"/>, except keeps fetching more responses until the beginning of the room is found, or the target message limit is reached /// </summary> - public async IAsyncEnumerable<MessagesResponse> GetManyMessagesAsync(string from = "", int limit = 100, string dir = "b", string filter = "", bool includeState = true, - bool fixForward = false, int chunkSize = 100) { + public async IAsyncEnumerable<MessagesResponse> GetManyMessagesAsync(string from = "", int limit = int.MaxValue, string dir = "b", string filter = "", bool includeState = true, + bool fixForward = false, int chunkSize = 250) { if (dir == "f" && fixForward) { var concat = new List<MessagesResponse>(); while (true) { @@ -241,10 +238,11 @@ public class GenericRoom { var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default }); + if (result is null) throw new Exception("Failed to deserialise members response"); // if (sw.ElapsedMilliseconds > 100) // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); - foreach (var resp in result.Chunk) { + foreach (var resp in result.Chunk ?? []) { if (resp?.Type != "m.room.member") continue; if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue; yield return resp; @@ -265,11 +263,12 @@ public class GenericRoom { var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default }); + if (result is null) throw new Exception("Failed to deserialise members response"); // if (sw.ElapsedMilliseconds > 100) // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); var members = new List<StateEventResponse>(); - foreach (var resp in result.Chunk) { + foreach (var resp in result.Chunk ?? []) { if (resp?.Type != "m.room.member") continue; if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue; members.Add(resp); @@ -461,7 +460,8 @@ public class GenericRoom { while (true) { try { return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(url, data)).Content.ReadFromJsonAsync<EventIdResponse>())!; - } catch (MatrixException e) { + } + catch (MatrixException e) { if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; throw; } @@ -555,5 +555,5 @@ public class GenericRoom { public class RoomIdResponse { [JsonPropertyName("room_id")] - public string RoomId { get; set; } = null!; + public string RoomId { get; set; } } diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs
index a674549..36bc828 100644 --- a/LibMatrix/Services/HomeserverProviderService.cs +++ b/LibMatrix/Services/HomeserverProviderService.cs
@@ -1,6 +1,5 @@ using System.Net.Http.Json; using ArcaneLibs.Collections; -using ArcaneLibs.Extensions; using LibMatrix.Homeservers; using LibMatrix.Responses; using Microsoft.Extensions.Logging; @@ -8,9 +7,9 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Services; public class HomeserverProviderService(ILogger<HomeserverProviderService> logger, HomeserverResolverService hsResolver) { - private static SemaphoreCache<AuthenticatedHomeserverGeneric> AuthenticatedHomeserverCache = new(); - private static SemaphoreCache<RemoteHomeserver> RemoteHomeserverCache = new(); - private static SemaphoreCache<FederationClient> FederationClientCache = new(); + private static readonly SemaphoreCache<AuthenticatedHomeserverGeneric> AuthenticatedHomeserverCache = new(); + private static readonly SemaphoreCache<RemoteHomeserver> RemoteHomeserverCache = new(); + private static readonly SemaphoreCache<FederationClient> FederationClientCache = new(); public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedWithToken(string homeserver, string accessToken, string? proxy = null, string? impersonatedMxid = null, bool useGeneric = false, bool enableClient = true, bool enableServer = true) { @@ -25,7 +24,7 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger if (!useGeneric) { var clientVersionsTask = rhs.GetClientVersionsAsync(); var serverVersionTask = rhs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult<ServerVersionResponse?>(null)!; - ClientVersionsResponse? clientVersions = new(); + ClientVersionsResponse clientVersions = new(); try { clientVersions = await clientVersionsTask; } @@ -48,6 +47,8 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger else { if (serverVersion is { Server.Name: "Synapse" }) hs = new AuthenticatedHomeserverSynapse(homeserver, wellKnownUris, proxy, accessToken); + else if (serverVersion is { Server.Name: "LibMatrix.HomeserverEmulator"}) + hs = new AuthenticatedHomeserverHSE(homeserver, wellKnownUris, proxy, accessToken); } } catch (Exception e) { @@ -67,7 +68,7 @@ public class HomeserverProviderService(ILogger<HomeserverProviderService> logger }); } - public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null, bool useCache = true, bool enableServer = true) => + public async Task<RemoteHomeserver> GetRemoteHomeserver(string homeserver, string? proxy = null, bool useCache = true, bool enableServer = false) => useCache ? await RemoteHomeserverCache.GetOrAdd($"{homeserver}{proxy}", async () => { return new RemoteHomeserver(homeserver, await hsResolver.ResolveHomeserverFromWellKnown(homeserver, enableServer: enableServer), proxy); }) diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs
index dc8e047..53cd2dd 100644 --- a/LibMatrix/Services/HomeserverResolverService.cs +++ b/LibMatrix/Services/HomeserverResolverService.cs
@@ -1,8 +1,4 @@ -using System.Collections.Concurrent; using System.Diagnostics; -using System.Net.Http.Json; -using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using ArcaneLibs.Collections; using ArcaneLibs.Extensions; @@ -13,9 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Services; public class HomeserverResolverService { - private readonly MatrixHttpClient _httpClient = new() { - // Timeout = TimeSpan.FromSeconds(60) // TODO: Re-implement this - }; + private readonly MatrixHttpClient _httpClient = new(); private static readonly SemaphoreCache<WellKnownUris> WellKnownCache = new(); @@ -26,53 +20,36 @@ public class HomeserverResolverService { if (logger is NullLogger<HomeserverResolverService>) { var stackFrame = new StackTrace(true).GetFrame(1); Console.WriteLine( - $"WARN | Null logger provided to HomeserverResolverService!\n{stackFrame.GetMethod().DeclaringType} at {stackFrame.GetFileName()}:{stackFrame.GetFileLineNumber()}"); + $"WARN | Null logger provided to HomeserverResolverService!\n{stackFrame?.GetMethod()?.DeclaringType?.ToString() ?? "null"} at {stackFrame?.GetFileName() ?? "null"}:{stackFrame?.GetFileLineNumber().ToString() ?? "null"}"); } } - // private static SemaphoreSlim _wellKnownSemaphore = new(1, 1); - public async Task<WellKnownUris> ResolveHomeserverFromWellKnown(string homeserver, bool enableClient = true, bool enableServer = true) { ArgumentNullException.ThrowIfNull(homeserver); return await WellKnownCache.GetOrAdd(homeserver, async () => { - // await _wellKnownSemaphore.WaitAsync(); _logger.LogTrace($"Resolving homeserver well-knowns: {homeserver}"); var client = enableClient ? _tryResolveClientEndpoint(homeserver) : null; var server = enableServer ? _tryResolveServerEndpoint(homeserver) : null; var res = new WellKnownUris(); - // try { if (client != null) - res.Client = await client ?? throw new Exception($"Could not resolve client URL for {homeserver}."); - // } - // catch (Exception e) { - // _logger.LogError(e, "Error resolving client well-known for {hs}", homeserver); - // } + res.Client = (await client)?.TrimEnd('/') ?? throw new Exception($"Could not resolve client URL for {homeserver}."); - // try { if (server != null) - res.Server = await server ?? throw new Exception($"Could not resolve server URL for {homeserver}."); - // } - // catch (Exception e) { - // _logger.LogError(e, "Error resolving server well-known for {hs}", homeserver); - // } + res.Server = (await server)?.TrimEnd('/') ?? throw new Exception($"Could not resolve server URL for {homeserver}."); _logger.LogInformation("Resolved well-knowns for {hs}: {json}", homeserver, res.ToJson(indent: false)); - // _wellKnownSemaphore.Release(); return res; }); } - // private async Task<WellKnownUris> InternalResolveHomeserverFromWellKnown(string homeserver) { - - // } - private async Task<string?> _tryResolveClientEndpoint(string homeserver) { ArgumentNullException.ThrowIfNull(homeserver); _logger.LogTrace("Resolving client well-known: {homeserver}", homeserver); ClientWellKnown? clientWellKnown = null; + homeserver = homeserver.TrimEnd('/'); // check if homeserver has a client well-known if (homeserver.StartsWith("https://")) { clientWellKnown = await _httpClient.TryGetFromJsonAsync<ClientWellKnown>($"{homeserver}/.well-known/matrix/client"); @@ -104,6 +81,7 @@ public class HomeserverResolverService { ArgumentNullException.ThrowIfNull(homeserver); _logger.LogTrace($"Resolving server well-known: {homeserver}"); ServerWellKnown? serverWellKnown = null; + homeserver = homeserver.TrimEnd('/'); // check if homeserver has a server well-known if (homeserver.StartsWith("https://")) { serverWellKnown = await _httpClient.TryGetFromJsonAsync<ServerWellKnown>($"{homeserver}/.well-known/matrix/server"); @@ -119,7 +97,7 @@ public class HomeserverResolverService { _logger.LogInformation("Server well-known for {hs}: {json}", homeserver, serverWellKnown?.ToJson() ?? "null"); if (!string.IsNullOrWhiteSpace(serverWellKnown?.Homeserver)) { - var resolved = serverWellKnown.Homeserver; + var resolved = serverWellKnown.Homeserver.TrimEnd('/'); if (resolved.StartsWith("https://") || resolved.StartsWith("http://")) return resolved; if (await MatrixHttpClient.CheckSuccessStatus($"https://{resolved}/_matrix/federation/v1/version")) @@ -130,14 +108,15 @@ public class HomeserverResolverService { } // fallback: most servers host C2S and S2S on the same domain - var clientUrl = await _tryResolveClientEndpoint(homeserver); + var clientUrl = (await _tryResolveClientEndpoint(homeserver)).TrimEnd('/'); if (clientUrl is not null && await MatrixHttpClient.CheckSuccessStatus($"{clientUrl}/_matrix/federation/v1/version")) return clientUrl; _logger.LogInformation("No server well-known for {server}...", homeserver); return null; } - + + [Obsolete("Use authenticated media, available on AuthenticatedHomeserverGeneric", true)] public async Task<string?> ResolveMediaUri(string homeserver, string mxc) { if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); if (mxc is null) throw new ArgumentNullException(nameof(mxc)); diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs
index 8b7e54b..5ffd43a 100644 --- a/LibMatrix/Services/ServiceInstaller.cs +++ b/LibMatrix/Services/ServiceInstaller.cs
@@ -1,5 +1,6 @@ +using LibMatrix.Services.WellKnownResolver; +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace LibMatrix.Services; @@ -9,7 +10,14 @@ public static class ServiceInstaller { services.AddSingleton(config ?? new RoryLibMatrixConfiguration()); //Add services - services.AddSingleton<HomeserverResolverService>(sp => new HomeserverResolverService(sp.GetRequiredService<ILogger<HomeserverResolverService>>())); + services.AddSingleton<ClientWellKnownResolver>(); + services.AddSingleton<ServerWellKnownResolver>(); + services.AddSingleton<SupportWellKnownResolver>(); + if (!services.Any(x => x.ServiceType == typeof(WellKnownResolverConfiguration))) + services.AddSingleton<WellKnownResolverConfiguration>(); + services.AddSingleton<WellKnownResolverService>(); + // Legacy + services.AddSingleton<HomeserverResolverService>(); services.AddSingleton<HomeserverProviderService>(); return services; diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs new file mode 100644
index 0000000..26a4c43 --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolverConfiguration.cs
@@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Services.WellKnownResolver; + +public class WellKnownResolverConfiguration { + /// <summary> + /// Allow transparent downgrades to plaintext HTTP if HTTPS fails + /// Enabling this is unsafe! + /// </summary> + [JsonPropertyName("allow_http")] + public bool AllowHttp { get; set; } = false; + + /// <summary> + /// Use DNS resolution if available, for resolving SRV records + /// </summary> + [JsonPropertyName("allow_dns")] + public bool AllowDns { get; set; } = true; + + /// <summary> + /// Use system resolver(s) if empty + /// </summary> + [JsonPropertyName("dns_servers")] + public List<string> DnsServers { get; set; } = new(); + + /// <summary> + /// Same as AllowDns, but for DNS over HTTPS - useful in browser contexts + /// </summary> + [JsonPropertyName("allow_doh")] + public bool AllowDoh { get; set; } = true; + + /// <summary> + /// Use DNS over HTTPS - useful in browser contexts + /// Disabled if empty + /// </summary> + [JsonPropertyName("doh_servers")] + public List<string> DohServers { get; set; } = new(); + + /// <summary> + /// Whether to allow fallback subdomain lookups + /// </summary> + [JsonPropertyName("allow_fallback_subdomains")] + public bool AllowFallbackSubdomains { get; set; } = true; + + /// <summary> + /// Fallback subdomains to try if the homeserver is not found + /// </summary> + [JsonPropertyName("fallback_subdomains")] + public List<string> FallbackSubdomains { get; set; } = ["matrix", "chat", "im"]; +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs new file mode 100644
index 0000000..4c78347 --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolverService.cs
@@ -0,0 +1,91 @@ +using System.Diagnostics; +using System.Text.Json.Serialization; +using LibMatrix.Extensions; +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace LibMatrix.Services.WellKnownResolver; + +public class WellKnownResolverService { + private readonly MatrixHttpClient _httpClient = new(); + + private readonly ILogger<WellKnownResolverService> _logger; + private readonly ClientWellKnownResolver _clientWellKnownResolver; + private readonly SupportWellKnownResolver _supportWellKnownResolver; + private readonly ServerWellKnownResolver _serverWellKnownResolver; + private readonly WellKnownResolverConfiguration _configuration; + + public WellKnownResolverService(ILogger<WellKnownResolverService> logger, ClientWellKnownResolver clientWellKnownResolver, SupportWellKnownResolver supportWellKnownResolver, + WellKnownResolverConfiguration configuration, ServerWellKnownResolver serverWellKnownResolver) { + _logger = logger; + _clientWellKnownResolver = clientWellKnownResolver; + _supportWellKnownResolver = supportWellKnownResolver; + _configuration = configuration; + _serverWellKnownResolver = serverWellKnownResolver; + if (logger is NullLogger<WellKnownResolverService>) { + var stackFrame = new StackTrace(true).GetFrame(1); + Console.WriteLine( + $"WARN | Null logger provided to WellKnownResolverService!\n{stackFrame?.GetMethod()?.DeclaringType?.ToString() ?? "null"} at {stackFrame?.GetFileName() ?? "null"}:{stackFrame?.GetFileLineNumber().ToString() ?? "null"}"); + } + } + + public async Task<WellKnownRecords> TryResolveWellKnownRecords(string homeserver, bool includeClient = true, bool includeServer = true, bool includeSupport = true, + WellKnownResolverConfiguration? config = null) { + WellKnownRecords records = new(); + _logger.LogDebug($"Resolving well-knowns for {homeserver}"); + if (includeClient && await _clientWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } clientResult) { + records.ClientWellKnown = clientResult; + } + + if (includeServer && await _serverWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } serverResult) { + records.ServerWellKnown = serverResult; + } + + if (includeSupport && await _supportWellKnownResolver.TryResolveWellKnown(homeserver, config ?? _configuration) is { } supportResult) { + records.SupportWellKnown = supportResult; + } + + return records; + } + + public class WellKnownRecords { + public WellKnownResolutionResult<ClientWellKnown?>? ClientWellKnown { get; set; } + public WellKnownResolutionResult<ServerWellKnown?>? ServerWellKnown { get; set; } + public WellKnownResolutionResult<SupportWellKnown?>? SupportWellKnown { get; set; } + } + + public class WellKnownResolutionResult<T> { + public WellKnownSource Source { get; set; } + public string? SourceUri { get; set; } + public T? Content { get; set; } + public List<WellKnownResolutionWarning> Warnings { get; set; } = []; + } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum WellKnownSource { + None, + Https, + Dns, + Http, + ManualCheck, + Search + } + + public struct WellKnownResolutionWarning { + public WellKnownResolutionWarningType Type { get; set; } + public string Message { get; set; } + [JsonIgnore] + public Exception? Exception { get; set; } + public string? ExceptionMessage => Exception?.Message; + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum WellKnownResolutionWarningType { + None, + Exception, + InvalidResponse, + Timeout, + SlowResponse + } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs new file mode 100644
index 0000000..cbe5b0a --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/BaseWellKnownResolver.cs
@@ -0,0 +1,52 @@ +using System.Diagnostics; +using System.Net.Http.Json; +using ArcaneLibs.Collections; +using LibMatrix.Extensions; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class BaseWellKnownResolver<T> where T : class, new() { + internal static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<T>> WellKnownCache = new() { + StoreNulls = false + }; + + internal static readonly MatrixHttpClient HttpClient = new(); + + internal async Task<WellKnownResolverService.WellKnownResolutionResult<T>> TryGetWellKnownFromUrl(string url, + WellKnownResolverService.WellKnownSource source) { + var sw = Stopwatch.StartNew(); + try { + var request = await HttpClient.GetAsync(url); + sw.Stop(); + var result = new WellKnownResolverService.WellKnownResolutionResult<T> { + Content = await request.Content.ReadFromJsonAsync<T>(), + Source = source, + SourceUri = url, + Warnings = [] + }; + + if (sw.ElapsedMilliseconds > 1000) { + // logger.LogWarning($"Support well-known resolution took {sw.ElapsedMilliseconds}ms: {url}"); + result.Warnings.Add(new() { + Type = WellKnownResolverService.WellKnownResolutionWarning.WellKnownResolutionWarningType.SlowResponse, + Message = $"Well-known resolution took {sw.ElapsedMilliseconds}ms" + }); + } + + return result; + } + catch (Exception e) { + return new WellKnownResolverService.WellKnownResolutionResult<T> { + Source = source, + SourceUri = url, + Warnings = [ + new() { + Exception = e, + Type = WellKnownResolverService.WellKnownResolutionWarning.WellKnownResolutionWarningType.Exception, + Message = e.Message + } + ] + }; + } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs new file mode 100644
index 0000000..f8de38d --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ClientWellKnownResolver.cs
@@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; +using ArcaneLibs.Collections; +using LibMatrix.Extensions; +using Microsoft.Extensions.Logging; +using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ClientWellKnown; +using ResultType = + LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ClientWellKnown?>; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class ClientWellKnownResolver(ILogger<ClientWellKnownResolver> logger, WellKnownResolverConfiguration configuration) + : BaseWellKnownResolver<ClientWellKnown> { + private static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> ClientWellKnownCache = new() { + StoreNulls = false + }; + + private static readonly MatrixHttpClient HttpClient = new(); + + public Task<WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { + config ??= configuration; + return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => { + logger.LogTrace($"Resolving client well-known: {homeserver}"); + + WellKnownResolverService.WellKnownResolutionResult<ClientWellKnown> result = + await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/client", WellKnownResolverService.WellKnownSource.Https); + if (result.Content != null) return result; + + + return result; + }); + } +} + +public class ClientWellKnown { + [JsonPropertyName("m.homeserver")] + public WellKnownHomeserver Homeserver { get; set; } + + public class WellKnownHomeserver { + [JsonPropertyName("base_url")] + public required string BaseUrl { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs new file mode 100644
index 0000000..a99185c --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/ServerWellKnownResolver.cs
@@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; +using ArcaneLibs.Collections; +using LibMatrix.Extensions; +using Microsoft.Extensions.Logging; +using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown; +using ResultType = + LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult<LibMatrix.Services.WellKnownResolver.WellKnownResolvers.ServerWellKnown?>; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class ServerWellKnownResolver(ILogger<ServerWellKnownResolver> logger, WellKnownResolverConfiguration configuration) + : BaseWellKnownResolver<ServerWellKnown> { + private static readonly SemaphoreCache<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> ClientWellKnownCache = new() { + StoreNulls = false + }; + + private static readonly MatrixHttpClient HttpClient = new(); + + public Task<WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown>> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { + config ??= configuration; + return ClientWellKnownCache.TryGetOrAdd(homeserver, async () => { + logger.LogTrace($"Resolving client well-known: {homeserver}"); + + WellKnownResolverService.WellKnownResolutionResult<ServerWellKnown> result = + await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/server", WellKnownResolverService.WellKnownSource.Https); + if (result.Content != null) return result; + + + return result; + }); + } +} + + +public class ServerWellKnown { + [JsonPropertyName("m.server")] + public string Homeserver { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs new file mode 100644
index 0000000..99313db --- /dev/null +++ b/LibMatrix/Services/WellKnownResolver/WellKnownResolvers/SupportWellKnownResolver.cs
@@ -0,0 +1,44 @@ +using System.Diagnostics; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using Microsoft.Extensions.Logging; +using WellKnownType = LibMatrix.Services.WellKnownResolver.WellKnownResolvers.SupportWellKnown; +using ResultType = LibMatrix.Services.WellKnownResolver.WellKnownResolverService.WellKnownResolutionResult< + LibMatrix.Services.WellKnownResolver.WellKnownResolvers.SupportWellKnown? +>; + +namespace LibMatrix.Services.WellKnownResolver.WellKnownResolvers; + +public class SupportWellKnownResolver(ILogger<SupportWellKnownResolver> logger, WellKnownResolverConfiguration configuration) : BaseWellKnownResolver<WellKnownType> { + public Task<ResultType> TryResolveWellKnown(string homeserver, WellKnownResolverConfiguration? config = null) { + config ??= configuration; + return WellKnownCache.TryGetOrAdd(homeserver, async () => { + logger.LogTrace($"Resolving support well-known: {homeserver}"); + + ResultType result = await TryGetWellKnownFromUrl($"https://{homeserver}/.well-known/matrix/support", WellKnownResolverService.WellKnownSource.Https); + if (result.Content != null) + return result; + + return null; + }); + } +} + +public class SupportWellKnown { + [JsonPropertyName("contacts")] + public List<WellKnownContact>? Contacts { get; set; } + + [JsonPropertyName("support_page")] + public Uri? SupportPage { get; set; } + + public class WellKnownContact { + [JsonPropertyName("email_address")] + public string? EmailAddress { get; set; } + + [JsonPropertyName("matrix_id")] + public string? MatrixId { get; set; } + + [JsonPropertyName("role")] + public required string Role { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index dc76622..ef760e1 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs
@@ -233,8 +233,6 @@ public class StateEventContentPolymorphicTypeInfoResolver : DefaultJsonTypeInfoR } */ -#endregion - /* public class ForgivingObjectConverter<T> : JsonConverter<T> where T : new() { public override T? Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options) { @@ -253,3 +251,5 @@ public class ForgivingObjectConverter<T> : JsonConverter<T> where T : new() { public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => JsonSerializer.Serialize<T>(writer, value, options); }*/ + +#endregion \ No newline at end of file diff --git a/LibMatrix/Utilities/CommonSyncFilters.cs b/LibMatrix/Utilities/CommonSyncFilters.cs
index bf8b987..503cc1f 100644 --- a/LibMatrix/Utilities/CommonSyncFilters.cs +++ b/LibMatrix/Utilities/CommonSyncFilters.cs
@@ -1,7 +1,7 @@ using System.Collections.Frozen; using LibMatrix.EventTypes.Common; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.EventTypes.Spec.State.Space; using LibMatrix.Filters; namespace LibMatrix.Utilities; diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
index 2819f80..6878b44 100644 --- a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs +++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
@@ -3,9 +3,7 @@ using LibMatrix.Homeservers; using LibMatrix.Responses; using LibMatrix.Services; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Xunit.Abstractions; -using Xunit.Sdk; namespace LibMatrix.Tests.Abstractions; diff --git a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
index 88b6758..13c5f58 100644 --- a/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs +++ b/Tests/LibMatrix.Tests/Abstractions/RoomAbstraction.cs
@@ -1,7 +1,6 @@ -using System.Diagnostics; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.EventTypes.Spec.State.Space; using LibMatrix.Homeservers; using LibMatrix.Responses; using LibMatrix.RoomTypes; @@ -53,7 +52,7 @@ public static class RoomAbstraction { return testRoom; } - private static SemaphoreSlim _spaceSemaphore = null!; + private static SemaphoreSlim _spaceSemaphore = new(1, 1); public static async Task<SpaceRoom> GetTestSpace(AuthenticatedHomeserverGeneric hs, int roomCount = 100, bool addSpaces = false, int spaceSizeReduction = 10) { _spaceSemaphore ??= new SemaphoreSlim(roomCount / spaceSizeReduction, roomCount / spaceSizeReduction); diff --git a/Tests/LibMatrix.Tests/Config.cs b/Tests/LibMatrix.Tests/Config.cs
index 045ea40..b59b238 100644 --- a/Tests/LibMatrix.Tests/Config.cs +++ b/Tests/LibMatrix.Tests/Config.cs
@@ -18,7 +18,7 @@ public class Config { { "matrix.org", "https://matrix-client.matrix.org" }, { "rory.gay", "https://matrix.rory.gay" }, { "feline.support", "https://matrix.feline.support" }, - { "transfem.dev", "https://matrix.transfem.dev" }, + { "transfem.dev", "https://matrix.transfem.dev/" }, { "the-apothecary.club", "https://the-apothecary.club" }, { "nixos.org", "https://matrix.nixos.org" }, { "fedora.im", "https://fedora.ems.host" } diff --git a/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs b/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs
index 01a0d2f..e3c4388 100644 --- a/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs +++ b/Tests/LibMatrix.Tests/Fixtures/TestFixture.cs
@@ -1,4 +1,3 @@ -using ArcaneLibs.Extensions; using LibMatrix.Services; using LibMatrix.Tests.Abstractions; using Microsoft.Extensions.Configuration; diff --git a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
index 1428464..98c8101 100644 --- a/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj +++ b/Tests/LibMatrix.Tests/LibMatrix.Tests.csproj
@@ -10,20 +10,20 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> - <PackageReference Include="xunit" Version="2.9.2" /> - <PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="8.2.2" /> - <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" /> + <PackageReference Include="xunit" Version="2.9.3" /> + <PackageReference Include="Xunit.Microsoft.DependencyInjection" Version="9.0.0" /> + <PackageReference Include="xunit.runner.visualstudio" Version="3.0.1"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="coverlet.collector" Version="6.0.2"> + <PackageReference Include="coverlet.collector" Version="6.0.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="Xunit.SkippableFact" Version="1.4.13"/> + <PackageReference Include="Xunit.SkippableFact" Version="1.5.23" /> </ItemGroup> <ItemGroup> diff --git a/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs b/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs
index 712e45a..29d37e0 100644 --- a/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs +++ b/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs
@@ -1,6 +1,5 @@ using ArcaneLibs.Extensions; using ArcaneLibs.Extensions.Streams; -using LibMatrix.Homeservers; using LibMatrix.Services; using LibMatrix.Tests.Abstractions; using LibMatrix.Tests.Fixtures; diff --git a/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs b/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs
index c7fde54..b6bde71 100644 --- a/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs +++ b/Tests/LibMatrix.Tests/Tests/CanonicalJsonTests.cs
@@ -2,13 +2,9 @@ using System.Collections.Frozen; using System.Diagnostics; using System.Text.Json; using LibMatrix.Extensions; -using LibMatrix.Services; -using LibMatrix.Tests.Abstractions; -using LibMatrix.Tests.DataTests; using LibMatrix.Tests.Fixtures; using Xunit.Abstractions; using Xunit.Microsoft.DependencyInjection.Abstracts; -using Xunit.Sdk; namespace LibMatrix.Tests.Tests; diff --git a/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests/ClientWellKnownResolverTests.cs b/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests/ClientWellKnownResolverTests.cs new file mode 100644
index 0000000..ea494fa --- /dev/null +++ b/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests/ClientWellKnownResolverTests.cs
@@ -0,0 +1,41 @@ +using LibMatrix.Services; +using LibMatrix.Services.WellKnownResolver.WellKnownResolvers; +using LibMatrix.Tests.Fixtures; +using Xunit.Abstractions; +using Xunit.Microsoft.DependencyInjection.Abstracts; + +namespace LibMatrix.Tests.Tests.HomeserverResolverTests; + +public class ClientWellKnownResolverTests : TestBed<TestFixture> { + private readonly Config _config; + private readonly ClientWellKnownResolver _resolver; + + public ClientWellKnownResolverTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { + _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}"); + _resolver = _fixture.GetService<ClientWellKnownResolver>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}"); + } + + [Fact] + public async Task ResolveServerClient() { + var tasks = _config.ExpectedHomeserverClientMappings.Select(async mapping => { + var server = await _resolver.TryResolveWellKnown(mapping.Key); + Assert.Equal(mapping.Value, server.Content.Homeserver.BaseUrl); + return server; + }).ToList(); + await Task.WhenAll(tasks); + } + + private async Task AssertClientWellKnown(string homeserver, string expected) { + var server = await _resolver.TryResolveWellKnown(homeserver); + Assert.Equal(expected, server.Content.Homeserver.BaseUrl); + } + + [Fact] + public Task ResolveMatrixOrg() => AssertClientWellKnown("matrix.org", "https://matrix-client.matrix.org"); + + [Fact] + public Task ResolveRoryGay() => AssertClientWellKnown("rory.gay", "https://matrix.rory.gay"); + + [Fact] + public Task ResolveTransfemDev() => AssertClientWellKnown("transfem.dev", "https://matrix.transfem.dev/"); +} \ No newline at end of file diff --git a/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs b/Tests/LibMatrix.Tests/Tests/LegacyHomeserverResolverTests.cs
index ef2426d..20dc4fb 100644 --- a/Tests/LibMatrix.Tests/Tests/HomeserverResolverTests.cs +++ b/Tests/LibMatrix.Tests/Tests/LegacyHomeserverResolverTests.cs
@@ -5,11 +5,11 @@ using Xunit.Microsoft.DependencyInjection.Abstracts; namespace LibMatrix.Tests.Tests; -public class HomeserverResolverTests : TestBed<TestFixture> { +public class LegacyHomeserverResolverTests : TestBed<TestFixture> { private readonly Config _config; private readonly HomeserverResolverService _resolver; - public HomeserverResolverTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { + public LegacyHomeserverResolverTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) { _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}"); _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}"); } @@ -33,11 +33,4 @@ public class HomeserverResolverTests : TestBed<TestFixture> { }).ToList(); await Task.WhenAll(tasks); } - - [Fact] - public async Task ResolveMedia() { - var media = await _resolver.ResolveMediaUri("matrix.org", "mxc://matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo"); - - Assert.Equal("https://matrix-client.matrix.org/_matrix/media/v3/download/matrix.org/eqwrRZRoPpNbcMeUwyXAuVRo", media); - } } \ No newline at end of file diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs
index 1ea3e18..7f53b08 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/BasicRoomEventTests/RoomNameTests.cs
@@ -1,4 +1,4 @@ -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Tests.Abstractions; using LibMatrix.Tests.Fixtures; using Xunit.Abstractions; diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs
index 9081a5a..97f4525 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomEventTests.cs
@@ -1,5 +1,3 @@ -using LibMatrix.Homeservers; -using LibMatrix.Services; using LibMatrix.Tests.Abstractions; using LibMatrix.Tests.Fixtures; using Xunit.Abstractions; diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
index 2e552e2..e0784c4 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomMembershipTests.cs
@@ -1,10 +1,6 @@ -using System.Diagnostics; -using System.Text; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; -using LibMatrix.Responses; -using LibMatrix.Services; using LibMatrix.Tests.Abstractions; using LibMatrix.Tests.Fixtures; using Xunit.Abstractions; diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
index 401b24f..fa9812f 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/RoomTests.cs
@@ -1,12 +1,8 @@ -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Text; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Homeservers; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Responses; -using LibMatrix.Services; using LibMatrix.Tests.Abstractions; using LibMatrix.Tests.Fixtures; using Xunit.Abstractions; diff --git a/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs b/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs
index 148b5fe..148fb70 100644 --- a/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs +++ b/Tests/LibMatrix.Tests/Tests/RoomTests/SpaceTests.cs
@@ -1,11 +1,7 @@ -using System.Diagnostics; -using System.Text; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Homeservers; +using LibMatrix.EventTypes.Spec.State.Space; using LibMatrix.Responses; using LibMatrix.RoomTypes; -using LibMatrix.Services; using LibMatrix.Tests.Abstractions; using LibMatrix.Tests.Fixtures; using Xunit.Abstractions; diff --git a/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj b/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj
index f21b34d..ee4fd58 100644 --- a/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj +++ b/Utilities/LibMatrix.DebugDataValidationApi/LibMatrix.DebugDataValidationApi.csproj
@@ -9,8 +9,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> </ItemGroup> <ItemGroup> diff --git a/Utilities/LibMatrix.DevTestBot/Bot/Commands/DbgAniRainbowTest.cs b/Utilities/LibMatrix.DevTestBot/Bot/Commands/DbgAniRainbowTest.cs
index f75c863..c91261f 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/Commands/DbgAniRainbowTest.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/Commands/DbgAniRainbowTest.cs
@@ -2,7 +2,6 @@ using System.Diagnostics; using LibMatrix.EventTypes.Spec; using LibMatrix.ExampleBot.Bot.Interfaces; using LibMatrix.Helpers; -using LibMatrix.RoomTypes; using LibMatrix.Services; namespace ModerationBot.Commands; diff --git a/Utilities/LibMatrix.DevTestBot/Bot/Commands/PingCommand.cs b/Utilities/LibMatrix.DevTestBot/Bot/Commands/PingCommand.cs
index 85c86a3..745c75d 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/Commands/PingCommand.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/Commands/PingCommand.cs
@@ -7,5 +7,16 @@ public class PingCommand : ICommand { public string Name { get; } = "ping"; public string Description { get; } = "Pong!"; - public async Task Invoke(CommandContext ctx) => await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: "pong!")); + // public async Task Invoke(CommandContext ctx) => await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: "pong!")); + public async Task Invoke(CommandContext ctx) { + // await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: "pong!")); + var count = ctx.Args.Length > 0 ? int.Parse(ctx.Args[0]) : 1; + var tasks = Enumerable.Range(0, count).Select(async i => { + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: $"!ping {i}", messageType: "m.text")); + await Task.Delay(1000); + }).ToList(); + await Task.WhenAll(tasks); + + await ctx.Room.SendMessageEventAsync(new RoomMessageEventContent(body: "Pong!")); + } } \ No newline at end of file diff --git a/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs b/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs
index fa80bfd..c650e2b 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/DevTestBot.cs
@@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.ExampleBot.Bot.Interfaces; using LibMatrix.Helpers; using LibMatrix.Homeservers; diff --git a/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs b/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs
index 90a95e4..221d1f4 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs
@@ -4,8 +4,9 @@ using LibMatrix.RoomTypes; namespace LibMatrix.ExampleBot.Bot.Interfaces; public class CommandContext { - public GenericRoom Room { get; set; } - public StateEventResponse MessageEvent { get; set; } - public string CommandName => (MessageEvent.TypedContent as RoomMessageEventContent).Body.Split(' ')[0][1..]; - public string[] Args => (MessageEvent.TypedContent as RoomMessageEventContent).Body.Split(' ')[1..]; + public required GenericRoom Room { get; init; } + public required StateEventResponse MessageEvent { get; init; } + public string CommandName => MessageContent.Body.Split(' ')[0][1..]; + public string[] Args => MessageContent.Body.Split(' ')[1..]; + private RoomMessageEventContent MessageContent => MessageEvent.TypedContent as RoomMessageEventContent ?? throw new Exception("Message content is not a RoomMessageEventContent"); } \ No newline at end of file diff --git a/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs b/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs new file mode 100644
index 0000000..9c8ad67 --- /dev/null +++ b/Utilities/LibMatrix.DevTestBot/Bot/PingTestBot.cs
@@ -0,0 +1,125 @@ +using System.Diagnostics.CodeAnalysis; +using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.ExampleBot.Bot.Interfaces; +using LibMatrix.Filters; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.Services; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace LibMatrix.ExampleBot.Bot; + +public class PingTestBot : IHostedService { + private readonly HomeserverProviderService _homeserverProviderService; + private readonly ILogger<DevTestBot> _logger; + private readonly DevTestBotConfiguration _configuration; + private readonly IEnumerable<ICommand> _commands; + + public PingTestBot(HomeserverProviderService homeserverProviderService, ILogger<DevTestBot> logger, + DevTestBotConfiguration configuration, IServiceProvider services) { + logger.LogInformation("{} instantiated!", GetType().Name); + _homeserverProviderService = homeserverProviderService; + _logger = logger; + _configuration = configuration; + _logger.LogInformation("Getting commands..."); + _commands = services.GetServices<ICommand>(); + _logger.LogInformation("Got {} commands!", _commands.Count()); + } + + /// <summary>Triggered when the application host is ready to start the service.</summary> + /// <param name="cancellationToken">Indicates that the start process has been aborted.</param> + [SuppressMessage("ReSharper", "FunctionNeverReturns")] + public async Task StartAsync(CancellationToken cancellationToken) { + // Directory.GetFiles("bot_data/cache").ToList().ForEach(File.Delete); + AuthenticatedHomeserverGeneric hs; + try { + hs = await _homeserverProviderService.GetAuthenticatedWithToken(_configuration.Homeserver, + _configuration.AccessToken); + } + catch (Exception e) { + _logger.LogError("{}", e.Message); + throw; + } + + var msg = new MessageBuilder().WithRainbowString("Meanwhile, I'm sitting here, still struggling with trying to rainbow. ^^'").Build(); + + var syncHelper = new SyncHelper(hs); + syncHelper.Filter = new SyncFilter { + Room = new SyncFilter.RoomFilter { + Timeline = new SyncFilter.RoomFilter.StateFilter() { + Limit = 1, + Senders = ["@me"] + }, + Rooms = ["!ping-v11:maunium.net"], + AccountData = new(types: []), + IncludeLeave = false + } + }; + + // await hs.GetRoom("!VJwxdebqoQlhGSEncc:codestorm.net").JoinAsync(); + + // foreach (var room in await hs.GetJoinedRooms()) { + // if(room.RoomId is "!OGEhHVWSdvArJzumhm:matrix.org") continue; + // foreach (var stateEvent in await room.GetStateAsync<List<StateEvent>>("")) { + // var _ = stateEvent.GetType; + // } + // _logger.LogInformation($"Got room state for {room.RoomId}!"); + // } + + // syncHelper.InviteReceivedHandlers.Add(async Task (args) => { + // var inviteEvent = + // args.Value.InviteState.Events.FirstOrDefault(x => + // x.Type == "m.room.member" && x.StateKey == hs.UserId); + // _logger.LogInformation( + // $"Got invite to {args.Key} by {inviteEvent.Sender} with reason: {(inviteEvent.TypedContent as RoomMemberEventContent).Reason}"); + // if (inviteEvent.Sender.EndsWith(":rory.gay") || inviteEvent.Sender == "@mxidupwitch:the-apothecary.club") + // try { + // var senderProfile = await hs.GetProfileAsync(inviteEvent.Sender); + // await hs.GetRoom(args.Key).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + // } + // catch (Exception e) { + // _logger.LogError("{}", e.ToString()); + // await hs.GetRoom(args.Key).LeaveAsync("I was unable to join the room: " + e); + // } + // }); + syncHelper.TimelineEventHandlers.Add(async @event => { + _logger.LogInformation( + "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(false, true)); + + var room = hs.GetRoom(@event.RoomId); + // _logger.LogInformation(eventResponse.ToJson(indent: false)); + if (@event is not { Sender: "@emma:rory.gay" }) return; + if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) + if (message is { MessageType: "m.text" } && message.Body.StartsWith(_configuration.Prefix)) { + var command = _commands.FirstOrDefault(x => x.Name == message.Body.Split(' ')[0][_configuration.Prefix.Length..]); + if (command == null) { + await room.SendMessageEventAsync( + new RoomMessageEventContent("m.text", "Command not found!")); + return; + } + + var ctx = new CommandContext { + Room = room, + MessageEvent = @event + }; + if (await command.CanInvoke(ctx)) + await command.Invoke(ctx); + else + await room.SendMessageEventAsync( + new RoomMessageEventContent("m.text", "You do not have permission to run this command!")); + } + }); + await syncHelper.RunSyncLoopAsync(cancellationToken: cancellationToken); + } + + /// <summary>Triggered when the application host is performing a graceful shutdown.</summary> + /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param> + public Task StopAsync(CancellationToken cancellationToken) { + _logger.LogInformation("Shutting down bot!"); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj b/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj
index e6ab408..7897e28 100644 --- a/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj +++ b/Utilities/LibMatrix.DevTestBot/LibMatrix.DevTestBot.csproj
@@ -18,13 +18,15 @@ </PropertyGroup> <ItemGroup> + <PackageReference Include="ArcaneLibs.StringNormalisation" Version="1.0.0-preview.20250307-202359" Condition="'$(Configuration)' == 'Release'" /> + <ProjectReference Include="..\..\ArcaneLibs\ArcaneLibs.StringNormalisation\ArcaneLibs.StringNormalisation.csproj" Condition="'$(Configuration)' == 'Debug'"/> <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> </ItemGroup> <ItemGroup> - <PackageReference Include="ArcaneLibs.StringNormalisation" Version="1.0.0-1.0.0-preview.20241122-053825"/> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1"/> </ItemGroup> + <ItemGroup> <Content Include="appsettings*.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> diff --git a/Utilities/LibMatrix.DevTestBot/Program.cs b/Utilities/LibMatrix.DevTestBot/Program.cs
index daaa65e..8eaaea8 100644 --- a/Utilities/LibMatrix.DevTestBot/Program.cs +++ b/Utilities/LibMatrix.DevTestBot/Program.cs
@@ -24,7 +24,7 @@ var host = Host.CreateDefaultBuilder(args).ConfigureServices((_, services) => { } // services.AddHostedService<ServerRoomSizeCalulator>(); - services.AddHostedService<DevTestBot>(); + services.AddHostedService<PingTestBot>(); }).UseConsoleLifetime().Build(); await host.RunAsync(); \ No newline at end of file diff --git a/Utilities/LibMatrix.DevTestBot/appsettings.json b/Utilities/LibMatrix.DevTestBot/appsettings.json
index db64c22..0d18b81 100644 --- a/Utilities/LibMatrix.DevTestBot/appsettings.json +++ b/Utilities/LibMatrix.DevTestBot/appsettings.json
@@ -8,7 +8,7 @@ }, "Bot": { "Homeserver": "rory.gay", - "AccessToken": "syt_xxxxxxxxxxxxxxxxx", - "Prefix": "!" + "AccessToken": "syt_ZW1tYQ_ATWykhpCWzjxkgTJHnAx_2AaoI7", + "Prefix": "$" } } \ No newline at end of file diff --git a/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj b/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj
index 4e3a128..78cdb67 100644 --- a/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj +++ b/Utilities/LibMatrix.E2eeTestKit/LibMatrix.E2eeTestKit.csproj
@@ -1,15 +1,15 @@ <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <PropertyGroup> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" /> - <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" /> + <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.1" PrivateAssets="all" /> </ItemGroup> <ItemGroup> diff --git a/Utilities/LibMatrix.E2eeTestKit/Pages/CSTJTest.razor b/Utilities/LibMatrix.E2eeTestKit/Pages/CSTJTest.razor
index 0d01428..2b787b3 100644 --- a/Utilities/LibMatrix.E2eeTestKit/Pages/CSTJTest.razor +++ b/Utilities/LibMatrix.E2eeTestKit/Pages/CSTJTest.razor
@@ -1,6 +1,5 @@ @page "/CSTJTest" @using System.Text.Json -@using System.Text.Json.Nodes @using LibMatrix.Extensions <PageTitle>Counter</PageTitle> diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/AuthController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/AuthController.cs
index 5550c26..d0eaed4 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/AuthController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/AuthController.cs
@@ -1,26 +1,21 @@ -using System.Security.Cryptography; using System.Text.Json.Nodes; using LibMatrix.HomeserverEmulator.Services; using LibMatrix.Responses; -using LibMatrix.Services; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; [ApiController] [Route("/_matrix/client/{version}/")] -public class AuthController(ILogger<AuthController> logger, UserStore userStore, TokenService tokenService, HSEConfiguration config) : ControllerBase { +public class AuthController(ILogger<AuthController> logger, UserStore userStore, TokenService tokenService, HseConfiguration config) : ControllerBase { [HttpPost("login")] public async Task<LoginResponse> Login(LoginRequest request) { if (!request.Identifier.User.StartsWith('@')) request.Identifier.User = $"@{request.Identifier.User}:{tokenService.GenerateServerName(HttpContext)}"; - if (request.Identifier.User.EndsWith("localhost")) - request.Identifier.User = request.Identifier.User.Replace("localhost", tokenService.GenerateServerName(HttpContext)); + // if (request.Identifier.User.EndsWith("localhost")) + // request.Identifier.User = request.Identifier.User.Replace("localhost", tokenService.GenerateServerName(HttpContext)); - var user = await userStore.GetUserById(request.Identifier.User); - if (user is null) { - user = await userStore.CreateUser(request.Identifier.User); - } + var user = await userStore.GetUserById(request.Identifier.User) ?? await userStore.CreateUser(request.Identifier.User); return user.Login(); } diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs
index b29edf5..d497ca6 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/DirectoryController.cs
@@ -1,11 +1,9 @@ -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; using LibMatrix.Homeservers; using LibMatrix.Responses; -using LibMatrix.Services; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; @@ -35,6 +33,70 @@ public class DirectoryController(ILogger<DirectoryController> logger, RoomStore }; } + [HttpGet("client/v3/publicRooms")] + public async Task<PublicRoomDirectoryResult> GetPublicRooms(int limit = 100, string? server = null, string? since = null) { + var rooms = roomStore._rooms.OrderByDescending(x => x.JoinedMembers.Count).AsEnumerable(); + + if (since != null) { + rooms = rooms.SkipWhile(x => x.RoomId != since).Skip(1); + } + + if (server != null) { + rooms = rooms.Where(x => x.State.Any(y => y.Type == RoomMemberEventContent.EventId && y.StateKey!.EndsWith(server))); + } + + var count = rooms.Count(); + rooms = rooms.Take(limit); + + return new PublicRoomDirectoryResult() { + Chunk = rooms.Select(x => new PublicRoomDirectoryResult.PublicRoomListItem() { + RoomId = x.RoomId, + Name = x.State.FirstOrDefault(y => y.Type == RoomNameEventContent.EventId)?.RawContent?["name"]?.ToString(), + Topic = x.State.FirstOrDefault(y => y.Type == RoomTopicEventContent.EventId)?.RawContent?["topic"]?.ToString(), + AvatarUrl = x.State.FirstOrDefault(y => y.Type == RoomAvatarEventContent.EventId)?.RawContent?["url"]?.ToString(), + GuestCanJoin = x.State.Any(y => y.Type == RoomGuestAccessEventContent.EventId && y.RawContent?["guest_access"]?.ToString() == "can_join"), + NumJoinedMembers = x.JoinedMembers.Count, + WorldReadable = x.State.Any(y => y.Type == RoomHistoryVisibilityEventContent.EventId && y.RawContent?["history_visibility"]?.ToString() == "world_readable"), + JoinRule = x.State.FirstOrDefault(y => y.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.ToString(), + CanonicalAlias = x.State.FirstOrDefault(y => y.Type == RoomCanonicalAliasEventContent.EventId)?.RawContent?["alias"]?.ToString() + }).ToList(), + NextBatch = count > limit ? rooms.Last().RoomId : null, + TotalRoomCountEstimate = count + }; + } + + [HttpPost("client/v3/publicRooms")] + public async Task<PublicRoomDirectoryResult> GetFilteredPublicRooms([FromBody] PublicRoomDirectoryRequest request, [FromQuery] string? server = null) { + var rooms = roomStore._rooms.OrderByDescending(x => x.JoinedMembers.Count).AsEnumerable(); + + if (request.Since != null) { + rooms = rooms.SkipWhile(x => x.RoomId != request.Since).Skip(1); + } + + if (server != null) { + rooms = rooms.Where(x => x.State.Any(y => y.Type == RoomMemberEventContent.EventId && y.StateKey!.EndsWith(server))); + } + + var count = rooms.Count(); + rooms = rooms.Take(request.Limit ?? 100); + + return new PublicRoomDirectoryResult() { + Chunk = rooms.Select(x => new PublicRoomDirectoryResult.PublicRoomListItem() { + RoomId = x.RoomId, + Name = x.State.FirstOrDefault(y => y.Type == RoomNameEventContent.EventId)?.RawContent?["name"]?.ToString(), + Topic = x.State.FirstOrDefault(y => y.Type == RoomTopicEventContent.EventId)?.RawContent?["topic"]?.ToString(), + AvatarUrl = x.State.FirstOrDefault(y => y.Type == RoomAvatarEventContent.EventId)?.RawContent?["url"]?.ToString(), + GuestCanJoin = x.State.Any(y => y.Type == RoomGuestAccessEventContent.EventId && y.RawContent?["guest_access"]?.ToString() == "can_join"), + NumJoinedMembers = x.JoinedMembers.Count, + WorldReadable = x.State.Any(y => y.Type == RoomHistoryVisibilityEventContent.EventId && y.RawContent?["history_visibility"]?.ToString() == "world_readable"), + JoinRule = x.State.FirstOrDefault(y => y.Type == RoomJoinRulesEventContent.EventId)?.RawContent?["join_rule"]?.ToString(), + CanonicalAlias = x.State.FirstOrDefault(y => y.Type == RoomCanonicalAliasEventContent.EventId)?.RawContent?["alias"]?.ToString() + }).ToList(), + NextBatch = count > request.Limit ? rooms.Last().RoomId : null, + TotalRoomCountEstimate = count + }; + } + #endregion #region User directory @@ -64,4 +126,29 @@ public class DirectoryController(ILogger<DirectoryController> logger, RoomStore } #endregion +} + +public class PublicRoomDirectoryRequest { + [JsonPropertyName("filter")] + public PublicRoomDirectoryFilter Filter { get; set; } + + [JsonPropertyName("include_all_networks")] + public bool IncludeAllNetworks { get; set; } + + [JsonPropertyName("limit")] + public int? Limit { get; set; } + + [JsonPropertyName("since")] + public string? Since { get; set; } + + [JsonPropertyName("third_party_instance_id")] + public string? ThirdPartyInstanceId { get; set; } + + public class PublicRoomDirectoryFilter { + [JsonPropertyName("generic_search_term")] + public string? GenericSearchTerm { get; set; } + + [JsonPropertyName("room_types")] + public List<string>? RoomTypes { get; set; } + } } \ No newline at end of file diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEAdmin/HEAdminController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEAdmin/HEAdminController.cs new file mode 100644
index 0000000..1fb3251 --- /dev/null +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEAdmin/HEAdminController.cs
@@ -0,0 +1,18 @@ +using LibMatrix.HomeserverEmulator.Services; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.HomeserverEmulator.Controllers; + +[ApiController] +[Route("/_hse/admin")] +public class HEAdminController(ILogger<HEAdminController> logger, UserStore userStore, RoomStore roomStore) : ControllerBase { + [HttpGet("users")] + public async Task<List<UserStore.User>> GetUsers() { + return userStore._users.ToList(); + } + + [HttpGet("rooms")] + public async Task<List<RoomStore.Room>> GetRooms() { + return roomStore._rooms.ToList(); + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEClient/HEClientController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEClient/HEClientController.cs new file mode 100644
index 0000000..85e4ddb --- /dev/null +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEClient/HEClientController.cs
@@ -0,0 +1,34 @@ +using ArcaneLibs.Collections; +using LibMatrix.HomeserverEmulator.Services; +using LibMatrix.Responses; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.HomeserverEmulator.Controllers; + +[ApiController] +[Route("/_hse/client/v1/external_profiles")] +public class HEClientController(ILogger<HEClientController> logger, UserStore userStore, TokenService tokenService) : ControllerBase { + [HttpGet] + public async Task<ObservableDictionary<string, LoginResponse>> GetExternalProfiles() { + var token = tokenService.GetAccessToken(HttpContext); + var user = await userStore.GetUserByToken(token); + + return user.AuthorizedSessions; + } + + [HttpPut("{name}")] + public async Task PutExternalProfile(string name, [FromBody] LoginResponse sessionData) { + var token = tokenService.GetAccessToken(HttpContext); + var user = await userStore.GetUserByToken(token); + + user.AuthorizedSessions[name] = sessionData; + } + + [HttpDelete("{name}")] + public async Task DeleteExternalProfile(string name) { + var token = tokenService.GetAccessToken(HttpContext); + var user = await userStore.GetUserByToken(token); + + user.AuthorizedSessions.Remove(name); + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEDebug/HEDebugController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEDebug/HEDebugController.cs
index 9e0c17c..ce47245 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEDebug/HEDebugController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/HEDebug/HEDebugController.cs
@@ -1,18 +1,18 @@ -using LibMatrix.HomeserverEmulator.Services; -using Microsoft.AspNetCore.Mvc; - -namespace LibMatrix.HomeserverEmulator.Controllers; - -[ApiController] -[Route("/_hsEmulator")] -public class HEDebugController(ILogger<HEDebugController> logger, UserStore userStore, RoomStore roomStore) : ControllerBase { - [HttpGet("users")] - public async Task<List<UserStore.User>> GetUsers() { - return userStore._users.ToList(); - } - - [HttpGet("rooms")] - public async Task<List<RoomStore.Room>> GetRooms() { - return roomStore._rooms.ToList(); - } +using LibMatrix.HomeserverEmulator.Services; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.HomeserverEmulator.Controllers; + +[ApiController] +[Route("/_hsEmulator")] +public class HEDebugController(ILogger<HEDebugController> logger, UserStore userStore, RoomStore roomStore) : ControllerBase { + [HttpGet("users")] + public async Task<List<UserStore.User>> GetUsers() { + return userStore._users.ToList(); + } + + [HttpGet("rooms")] + public async Task<List<RoomStore.Room>> GetRooms() { + return roomStore._rooms.ToList(); + } } \ No newline at end of file diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs
index 1fb427e..245770e 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/LegacyController.cs
@@ -1,12 +1,8 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using System.Text.Json.Nodes; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; -using LibMatrix.Responses; -using LibMatrix.Services; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs
index 59d37ff..6048bbb 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Media/MediaController.cs
@@ -1,9 +1,9 @@ using System.Text.Json.Nodes; using System.Text.RegularExpressions; -using ArcaneLibs.Collections; using LibMatrix.HomeserverEmulator.Services; using LibMatrix.Services; using Microsoft.AspNetCore.Mvc; +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace LibMatrix.HomeserverEmulator.Controllers.Media; @@ -13,12 +13,12 @@ public class MediaController( ILogger<MediaController> logger, TokenService tokenService, UserStore userStore, - HSEConfiguration cfg, + HseConfiguration cfg, HomeserverResolverService hsResolver, MediaStore mediaStore) : ControllerBase { [HttpPost("upload")] - public async Task<object> UploadMedia([FromHeader(Name = "Content-Type")] string ContentType, [FromQuery] string filename, [FromBody] Stream file) { + public async Task<object> UploadMedia([FromHeader(Name = "Content-Type")] string contentType, [FromQuery] string filename, [FromBody] Stream file) { var token = tokenService.GetAccessTokenOrNull(HttpContext); if (token == null) throw new MatrixException() { @@ -76,7 +76,9 @@ public class MediaController( if (cfg.StoreData) { var path = Path.Combine(cfg.DataStoragePath, "media", serverName, mediaId); if (!System.IO.File.Exists(path)) { - var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + // var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + var homeserver = (await hsResolver.ResolveHomeserverFromWellKnown(serverName)).Client; + var mediaUrl = homeserver is null ? null : $"{homeserver}/_matrix/media/v3/download/"; if (mediaUrl is null) throw new MatrixException() { ErrorCode = "M_NOT_FOUND", @@ -91,7 +93,9 @@ public class MediaController( return new FileStream(path, FileMode.Open); } else { - var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + // var mediaUrl = await hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + var homeserver = (await hsResolver.ResolveHomeserverFromWellKnown(serverName)).Client; + var mediaUrl = homeserver is null ? null : $"{homeserver}/_matrix/media/v3/download/"; if (mediaUrl is null) throw new MatrixException() { ErrorCode = "M_NOT_FOUND", diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs
index 7d735f7..6c57cc4 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomMembersController.cs
@@ -1,4 +1,4 @@ -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; using Microsoft.AspNetCore.Mvc; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
index 7a16ace..61195b8 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomTimelineController.cs
@@ -1,10 +1,10 @@ -using System.Collections.Immutable; using System.Diagnostics; using System.Text.Json.Nodes; using ArcaneLibs; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.EventTypes.Spec.State.Space; using LibMatrix.Helpers; using LibMatrix.HomeserverEmulator.Extensions; using LibMatrix.HomeserverEmulator.Services; @@ -21,6 +21,7 @@ public class RoomTimelineController( TokenService tokenService, UserStore userStore, RoomStore roomStore, + HseConfiguration hseConfig, HomeserverProviderService hsProvider) : ControllerBase { [HttpPut("send/{eventType}/{txnId}")] public async Task<EventIdResponse> SendMessage(string roomId, string eventType, string txnId, [FromBody] JsonObject content) { @@ -34,21 +35,21 @@ public class RoomTimelineController( Error = "Room not found" }; - if (!room.JoinedMembers.Any(x => x.StateKey == user.UserId)) - throw new MatrixException() { - ErrorCode = "M_FORBIDDEN", - Error = "User is not in the room" - }; - var evt = new StateEvent() { RawContent = content, Type = eventType }.ToStateEvent(user, room); - room.Timeline.Add(evt); if (evt.Type == RoomMessageEventContent.EventId && (evt.TypedContent as RoomMessageEventContent).Body.StartsWith("!hse")) _ = Task.Run(() => HandleHseCommand(evt, room, user)); - // else + + if (!room.JoinedMembers.Any(x => x.StateKey == user.UserId)) + throw new MatrixException() { + ErrorCode = "M_FORBIDDEN", + Error = "User is not in the room" + }; + + room.Timeline.Add(evt); return new() { EventId = evt.EventId @@ -256,7 +257,8 @@ public class RoomTimelineController( room.Timeline.Add(new StateEventResponse() { Type = RoomMessageEventContent.EventId, TypedContent = content, - Sender = $"@hse:{tokenService.GenerateServerName(HttpContext)}", + // Sender = $"@hse:{tokenService.GenerateServerName(HttpContext)}", + Sender = $"@hse:{hseConfig.ServerName}", RoomId = room.RoomId, EventId = "$" + string.Join("", Random.Shared.GetItems("abcdefghijklmnopqrstuvwxyzABCDEFGHIJLKMNOPQRSTUVWXYZ0123456789".ToCharArray(), 100)), OriginServerTs = DateTimeOffset.Now.ToUnixTimeMilliseconds() @@ -299,7 +301,7 @@ public class RoomTimelineController( InternalSendMessage(room, url + "&i=" + i); if (i % 5000 == 0 || i == 9999) { - Thread.Sleep(5000); + // Thread.Sleep(1000); do { InternalSendMessage(room, @@ -320,7 +322,7 @@ public class RoomTimelineController( var count = 1000; for (int i = 0; i < count; i++) { var crq = new CreateRoomRequest() { - Name = "Test room", + Name = $"Test room {i}", CreationContent = new() { ["version"] = "11" }, diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
index c24e6e9..9dae2e5 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
@@ -1,5 +1,4 @@ using System.Text.Json.Serialization; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; using LibMatrix.Responses; @@ -62,7 +61,7 @@ public class RoomsController(ILogger<RoomsController> logger, TokenService token var room = new RoomStore.Room($"!{Guid.NewGuid()}:{tokenService.GenerateServerName(HttpContext)}"); var eventTypesToTransfer = new[] { - RoomServerACLEventContent.EventId, + RoomServerAclEventContent.EventId, RoomEncryptionEventContent.EventId, RoomNameEventContent.EventId, RoomAvatarEventContent.EventId, diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs
index f585eed..86c9f6a 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/SyncController.cs
@@ -1,7 +1,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Extensions; using LibMatrix.HomeserverEmulator.Services; using LibMatrix.Responses; @@ -11,7 +11,7 @@ namespace LibMatrix.HomeserverEmulator.Controllers; [ApiController] [Route("/_matrix/client/{version}/")] -public class SyncController(ILogger<SyncController> logger, TokenService tokenService, UserStore userStore, RoomStore roomStore, HSEConfiguration cfg) : ControllerBase { +public class SyncController(ILogger<SyncController> logger, TokenService tokenService, UserStore userStore, RoomStore roomStore, HseConfiguration cfg) : ControllerBase { [HttpGet("sync")] [SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action", Justification = "Endpoint is expected to wait until data is available or timeout.")] public async Task<SyncResponse> Sync([FromQuery] string? since = null, [FromQuery] int? timeout = 5000) { @@ -34,10 +34,18 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe newSyncState = syncState.Clone(); var newSyncToken = Guid.NewGuid().ToString(); + long stallTime = 100; do { syncResp = IncrementalSync(user, session, syncState); syncResp.NextBatch = newSyncToken; - } while (!await HasDataOrStall(syncResp) && sw.ElapsedMilliseconds < timeout); + + var hasData = await HasDataOrStall(syncResp); + if (!hasData) { + await Task.Delay((int)Math.Min(stallTime, Math.Max(0, (timeout ?? 10) - sw.ElapsedMilliseconds))); + stallTime *= 2; + } + else break; + } while (sw.ElapsedMilliseconds < timeout); if (sw.ElapsedMilliseconds > timeout) { logger.LogTrace("Sync timed out after {Elapsed}", sw.Elapsed); @@ -49,6 +57,7 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe session.SyncStates[syncResp.NextBatch] = RecalculateSyncStates(newSyncState, syncResp); logger.LogTrace("Responding to sync after {totalElapsed}", sw.Elapsed); + // logger.LogTrace(syncResp.ToJson(ignoreNull: true)); return syncResp; } @@ -135,6 +144,7 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe }; // step 1: check previously synced rooms + int updatedRooms = 0; foreach (var (roomId, roomPosition) in syncState.RoomPositions) { var room = roomStore.GetRoomById(roomId); if (room == null) { @@ -147,9 +157,11 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe var newTimelineEvents = room.Timeline.Skip(roomPosition.TimelinePosition).ToList(); var newAccountDataEvents = room.AccountData[user.UserId].Skip(roomPosition.AccountDataPosition).ToList(); if (newTimelineEvents.Count == 0 && newAccountDataEvents.Count == 0) continue; + if (updatedRooms++ >= 50) break; // performance cap data.Join[room.RoomId] = new() { State = new(newTimelineEvents.GetCalculatedState()), - Timeline = new(newTimelineEvents, false) + Timeline = new(newTimelineEvents, false), + AccountData = new(newAccountDataEvents) }; } } @@ -160,11 +172,11 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe } // step 2: check newly joined rooms - var untrackedRooms = roomStore._rooms.Where(r => !syncState.RoomPositions.ContainsKey(r.RoomId)).ToList(); + // var untrackedRooms = roomStore._rooms.Where(r => !syncState.RoomPositions.ContainsKey(r.RoomId)).ToList(); var allJoinedRooms = roomStore.GetRoomsByMember(user.UserId).ToArray(); if (allJoinedRooms.Length == 0) return data; - var rooms = Random.Shared.GetItems(allJoinedRooms, Math.Min(allJoinedRooms.Length, 50)); + var rooms = Random.Shared.GetItems(allJoinedRooms, Math.Min(allJoinedRooms.Length, 1)); foreach (var membership in rooms) { var membershipContent = membership.TypedContent as RoomMemberEventContent ?? throw new InvalidOperationException("Membership event content is not RoomMemberEventContent"); @@ -211,7 +223,7 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe private bool HasData(SyncResponse resp) { return resp.Rooms?.Invite?.Count > 0 || resp.Rooms?.Join?.Count > 0 || resp.Rooms?.Leave?.Count > 0; } - + private async Task<bool> HasDataOrStall(SyncResponse resp) { // logger.LogTrace("Checking if sync response has data: {resp}", resp.ToJson(indent: false, ignoreNull: true)); // if (resp.AccountData?.Events?.Count > 0) return true; @@ -246,29 +258,23 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe // }; var hasData = resp is { - AccountData: { - Events: { Count: > 0 } - } + AccountData.Events.Count: > 0 } or { - Presence: { - Events: { Count: > 0 } - } + Presence.Events.Count: > 0 } or { DeviceLists: { - Changed: { Count: > 0 }, - Left: { Count: > 0 } + Changed.Count: > 0, + Left.Count: > 0 } } or { - ToDevice: { - Events: { Count: > 0 } - } + ToDevice.Events.Count: > 0 } or { Rooms: { - Invite: { Count: > 0 } + Invite.Count: > 0 } or { - Join: { Count: > 0 } + Join.Count: > 0 } or { - Leave: { Count: > 0 } + Leave.Count: > 0 } }; @@ -278,7 +284,7 @@ public class SyncController(ILogger<SyncController> logger, TokenService tokenSe if (!hasData) { // logger.LogDebug($"Sync response has no data, stalling for 1000ms: {resp.ToJson(indent: false, ignoreNull: true)}"); - await Task.Delay(10); + // await Task.Delay(100); } return hasData; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/AccountDataController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/AccountDataController.cs
index a32d283..41cf62a 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/AccountDataController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/AccountDataController.cs
@@ -1,9 +1,5 @@ using System.Text.Json.Nodes; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Filters; using LibMatrix.HomeserverEmulator.Services; -using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/FilterController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/FilterController.cs
index bdee7bc..f163a8e 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/FilterController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/FilterController.cs
@@ -1,9 +1,5 @@ -using System.Text.Json.Nodes; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.Filters; using LibMatrix.HomeserverEmulator.Services; -using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs
index 98c41da..7afdcb5 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/ProfileController.cs
@@ -1,9 +1,5 @@ using System.Text.Json.Nodes; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Filters; using LibMatrix.HomeserverEmulator.Services; -using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs
index 2be3896..40f3667 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Users/UserController.cs
@@ -1,10 +1,6 @@ -using System.Text.Json.Nodes; using System.Text.Json.Serialization; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; -using LibMatrix.Filters; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Services; -using LibMatrix.Responses; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
index 0c3bde6..93e4b4f 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/VersionsController.cs
@@ -1,8 +1,6 @@ -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using LibMatrix.Homeservers; using LibMatrix.Responses; -using LibMatrix.Services; using Microsoft.AspNetCore.Mvc; namespace LibMatrix.HomeserverEmulator.Controllers; @@ -30,6 +28,11 @@ public class VersionsController(ILogger<WellKnownController> logger) : Controlle "v1.6", "v1.7", "v1.8", + "v1.9", + "v1.10", + "v1.11", + "v1.12", + "v1.13" }, UnstableFeatures = new() }; diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/WellKnownController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/WellKnownController.cs
index 97e460d..3cec712 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/WellKnownController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/WellKnownController.cs
@@ -1,32 +1,34 @@ -using System.Text.Json.Nodes; -using LibMatrix.Services; -using Microsoft.AspNetCore.Mvc; - -namespace LibMatrix.HomeserverEmulator.Controllers; - -[ApiController] -[Route("/.well-known/matrix/")] -public class WellKnownController(ILogger<WellKnownController> logger) : ControllerBase { - [HttpGet("client")] - public JsonObject GetClientWellKnown() { - var obj = new JsonObject() { - ["m.homeserver"] = new JsonObject() { - ["base_url"] = $"{Request.Scheme}://{Request.Host}" - } - }; - - logger.LogInformation("Serving client well-known: {}", obj); - - return obj; - } - [HttpGet("server")] - public JsonObject GetServerWellKnown() { - var obj = new JsonObject() { - ["m.server"] = $"{Request.Scheme}://{Request.Host}" - }; - - logger.LogInformation("Serving server well-known: {}", obj); - - return obj; - } +using System.Text.Json.Nodes; +using Microsoft.AspNetCore.Mvc; + +namespace LibMatrix.HomeserverEmulator.Controllers; + +[ApiController] +[Route("/.well-known/matrix/")] +public class WellKnownController(ILogger<WellKnownController> logger) : ControllerBase { + [HttpGet("client")] + public JsonObject GetClientWellKnown() { + var obj = new JsonObject() { + ["m.homeserver"] = new JsonObject() { + // ["base_url"] = $"{Request.Scheme}://{Request.Host}" + ["base_url"] = $"https://{Request.Host}" + } + }; + + logger.LogInformation("Serving client well-known: {}", obj); + + return obj; + } + + [HttpGet("server")] + public JsonObject GetServerWellKnown() { + var obj = new JsonObject() { + // ["m.server"] = $"{Request.Scheme}://{Request.Host}" + ["m.server"] = $"https://{Request.Host}" + }; + + logger.LogInformation("Serving server well-known: {}", obj); + + return obj; + } } \ No newline at end of file diff --git a/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj b/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj
index f5ff2d1..5178012 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj +++ b/Utilities/LibMatrix.HomeserverEmulator/LibMatrix.HomeserverEmulator.csproj
@@ -11,8 +11,8 @@ <ItemGroup> <PackageReference Include="EasyCompressor.LZMA" Version="2.0.2" /> - <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" /> + <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.1" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> </ItemGroup> <ItemGroup> @@ -1027,6 +1027,8 @@ <_ContentIncludedByDefault Remove="data\rooms\!ffda8b8c-baf0-4938-bda5-12d30ef39fe8.json" /> <_ContentIncludedByDefault Remove="data\rooms\!ffe1fed4-8e49-4584-8f74-a1f9a58b70ed.json" /> <_ContentIncludedByDefault Remove="data\rooms\!ffffc7e7-4b1e-40a9-9f24-ebd95267e758.json" /> + <_ContentIncludedByDefault Remove="data\users\@emma:hse.localhost\tokens.json" /> + <_ContentIncludedByDefault Remove="data\users\@emma:hse.localhost\user.json" /> </ItemGroup> </Project> diff --git a/Utilities/LibMatrix.HomeserverEmulator/Program.cs b/Utilities/LibMatrix.HomeserverEmulator/Program.cs
index 9ea6fce..c72df5a 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Program.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Program.cs
@@ -1,7 +1,6 @@ using System.Net.Mime; using System.Text.Json.Serialization; using LibMatrix; -using LibMatrix.HomeserverEmulator.Extensions; using LibMatrix.HomeserverEmulator.Services; using LibMatrix.Services; using Microsoft.AspNetCore.Diagnostics; @@ -32,7 +31,7 @@ builder.Services.AddSwaggerGen(c => { }); builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); -builder.Services.AddSingleton<HSEConfiguration>(); +builder.Services.AddSingleton<HseConfiguration>(); builder.Services.AddSingleton<UserStore>(); builder.Services.AddSingleton<RoomStore>(); builder.Services.AddSingleton<MediaStore>(); @@ -77,7 +76,7 @@ app.UseExceptionHandler(exceptionHandlerApp => { var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>(); if (exceptionHandlerPathFeature?.Error is not null) - Console.WriteLine(exceptionHandlerPathFeature.Error.ToString()!); + Console.WriteLine(exceptionHandlerPathFeature.Error.ToString()); if (exceptionHandlerPathFeature?.Error is MatrixException mxe) { context.Response.StatusCode = mxe.ErrorCode switch { @@ -86,14 +85,14 @@ app.UseExceptionHandler(exceptionHandlerApp => { _ => StatusCodes.Status500InternalServerError }; context.Response.ContentType = MediaTypeNames.Application.Json; - await context.Response.WriteAsync(mxe.GetAsJson()!); + await context.Response.WriteAsync(mxe.GetAsJson()); } else { context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = MediaTypeNames.Application.Json; await context.Response.WriteAsync(new MatrixException() { ErrorCode = "M_UNKNOWN", - Error = exceptionHandlerPathFeature?.Error.ToString() + Error = exceptionHandlerPathFeature?.Error?.ToString() ?? "Unknown error" }.GetAsJson()); } }); diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs
index bcfb629..04ce050 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/HSEConfiguration.cs
@@ -4,12 +4,12 @@ using ArcaneLibs.Extensions; namespace LibMatrix.HomeserverEmulator.Services; -public class HSEConfiguration { - private static ILogger<HSEConfiguration> _logger; - public static HSEConfiguration Current { get; set; } +public class HseConfiguration { + private static ILogger<HseConfiguration> _logger; + public static HseConfiguration Current { get; set; } [RequiresUnreferencedCode("Uses reflection binding")] - public HSEConfiguration(ILogger<HSEConfiguration> logger, IConfiguration config, HostBuilderContext host) { + public HseConfiguration(ILogger<HseConfiguration> logger, IConfiguration config, HostBuilderContext host) { Current = this; _logger = logger; logger.LogInformation("Loading configuration for environment: {}...", host.HostingEnvironment.EnvironmentName); @@ -22,15 +22,15 @@ public class HSEConfiguration { _logger.LogInformation("Configuration loaded: {}", this.ToJson()); } - public string CacheStoragePath { get; set; } + public required string CacheStoragePath { get; set; } - public string DataStoragePath { get; set; } + public required string DataStoragePath { get; set; } - public bool StoreData { get; set; } = true; + public required bool StoreData { get; set; } = true; - public string ServerName { get; set; } = "localhost"; + public required string ServerName { get; set; } = "localhost"; - public bool UnknownSyncTokenIsInitialSync { get; set; } = true; + public required bool UnknownSyncTokenIsInitialSync { get; set; } = true; private static string ExpandPath(string path, bool retry = true) { _logger.LogInformation("Expanding path `{}`", path); diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/MediaStore.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/MediaStore.cs
index 00f2a42..7945d3a 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/MediaStore.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/MediaStore.cs
@@ -4,18 +4,18 @@ using LibMatrix.Services; namespace LibMatrix.HomeserverEmulator.Services; public class MediaStore { - private readonly HSEConfiguration _config; + private readonly HseConfiguration _config; private readonly HomeserverResolverService _hsResolver; - private List<MediaInfo> index = new(); + private List<MediaInfo> _mediaIndex = new(); - public MediaStore(HSEConfiguration config, HomeserverResolverService hsResolver) { + public MediaStore(HseConfiguration config, HomeserverResolverService hsResolver) { _config = config; _hsResolver = hsResolver; if (config.StoreData) { var path = Path.Combine(config.DataStoragePath, "media"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); if (File.Exists(Path.Combine(path, "index.json"))) - index = JsonSerializer.Deserialize<List<MediaInfo>>(File.ReadAllText(Path.Combine(path, "index.json"))); + _mediaIndex = JsonSerializer.Deserialize<List<MediaInfo>>(File.ReadAllText(Path.Combine(path, "index.json"))); } else Console.WriteLine("Data storage is disabled, not loading rooms from disk"); @@ -36,7 +36,9 @@ public class MediaStore { if (_config.StoreData) { var path = Path.Combine(_config.DataStoragePath, "media", serverName, mediaId); if (!File.Exists(path)) { - var mediaUrl = await _hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + // var mediaUrl = await _hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + var homeserver = (await _hsResolver.ResolveHomeserverFromWellKnown(serverName)).Client; + var mediaUrl = homeserver is null ? null : $"{homeserver}/_matrix/media/v3/download/"; if (mediaUrl is null) throw new MatrixException() { ErrorCode = "M_NOT_FOUND", @@ -50,7 +52,9 @@ public class MediaStore { return new FileStream(path, FileMode.Open); } else { - var mediaUrl = await _hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + // var mediaUrl = await _hsResolver.ResolveMediaUri(serverName, $"mxc://{serverName}/{mediaId}"); + var homeserver = (await _hsResolver.ResolveHomeserverFromWellKnown(serverName)).Client; + var mediaUrl = homeserver is null ? null : $"{homeserver}/_matrix/media/v3/download/"; if (mediaUrl is null) throw new MatrixException() { ErrorCode = "M_NOT_FOUND", diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs
index 0128ba6..0603a2d 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/PaginationTokenResolverService.cs
@@ -35,15 +35,15 @@ public class PaginationTokenResolverService(ILogger<PaginationTokenResolverServi } } - public async Task<StateEventResponse?> ResolveTokenToEvent(string token, RoomStore.Room room) { + public Task<StateEventResponse?> ResolveTokenToEvent(string token, RoomStore.Room room) { if (token.StartsWith('$')) { //we have an event ID logger.LogTrace("ResolveTokenToEvent(EventId({token}), Room({room})): searching for event...", token, room.RoomId); var evt = room.Timeline.SingleOrDefault(x => x.EventId == token); - if (evt is not null) return evt; + if (evt is not null) return Task.FromResult(evt); logger.LogTrace("ResolveTokenToEvent({token}, Room({room})): event not in requested room...", token, room.RoomId); - return null; + return Task.FromResult<StateEventResponse?>(null); } else { // we have a sync token diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs
index c798cce..b6fe7c2 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/RoomStore.cs
@@ -2,13 +2,12 @@ using System.Collections.Concurrent; using System.Collections.Frozen; using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Collections.Specialized; using System.Text.Json; using System.Text.Json.Nodes; using ArcaneLibs; using ArcaneLibs.Collections; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.HomeserverEmulator.Controllers.Rooms; using LibMatrix.Responses; @@ -19,14 +18,14 @@ public class RoomStore { public ConcurrentBag<Room> _rooms = new(); private FrozenDictionary<string, Room> _roomsById = FrozenDictionary<string, Room>.Empty; - public RoomStore(ILogger<RoomStore> logger, HSEConfiguration config) { + public RoomStore(ILogger<RoomStore> logger, HseConfiguration config) { _logger = logger; if (config.StoreData) { var path = Path.Combine(config.DataStoragePath, "rooms"); if (!Directory.Exists(path)) Directory.CreateDirectory(path); foreach (var file in Directory.GetFiles(path)) { var room = JsonSerializer.Deserialize<Room>(File.ReadAllText(file)); - if (room is not null) _rooms.Add(room); + if (room is not null && room.State.Any(x => x.Type == RoomCreateEventContent.EventId)) _rooms.Add(room); } } else @@ -35,25 +34,8 @@ public class RoomStore { RebuildIndexes(); } - private SemaphoreSlim a = new(1, 1); private void RebuildIndexes() { - // a.Wait(); - // lock (_roomsById) - // _roomsById = new ConcurrentDictionary<string, Room>(_rooms.ToDictionary(u => u.RoomId)); - // foreach (var room in _rooms) { - // _roomsById.AddOrUpdate(room.RoomId, room, (key, old) => room); - // } - // - // var roomsArr = _rooms.ToArray(); - // foreach (var (id, room) in _roomsById) { - // if (!roomsArr.Any(x => x.RoomId == id)) - // _roomsById.TryRemove(id, out _); - // } - - // _roomsById = new ConcurrentDictionary<string, Room>(_rooms.ToDictionary(u => u.RoomId)); _roomsById = _rooms.ToFrozenDictionary(u => u.RoomId); - - // a.Release(); } public Room? GetRoomById(string roomId, bool createIfNotExists = false) { @@ -64,18 +46,19 @@ public class RoomStore { if (!createIfNotExists) return null; - return CreateRoom(new() { }); + return CreateRoom(new()); } public Room CreateRoom(CreateRoomRequest request, UserStore.User? user = null) { var room = new Room(roomId: $"!{Guid.NewGuid().ToString()}"); var newCreateEvent = new StateEvent() { Type = RoomCreateEventContent.EventId, - RawContent = new() { } + RawContent = new() }; foreach (var (key, value) in request.CreationContent) { newCreateEvent.RawContent[key] = value.DeepClone(); + Console.WriteLine($"RawContent[{key}] = {value.DeepClone().ToJson(ignoreNull: true)}"); } if (user != null) { @@ -207,7 +190,7 @@ public class RoomStore { : JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(request.TypedContent))) }; Timeline.Add(state); - if(state.StateKey != null) + if (state.StateKey != null) RebuildState(); return state; } @@ -226,32 +209,36 @@ public class RoomStore { } // public async Task SaveDebounced() { - // if (!HSEConfiguration.Current.StoreData) return; - // await _debounceCts.CancelAsync(); - // _debounceCts = new CancellationTokenSource(); - // try { - // await Task.Delay(250, _debounceCts.Token); - // // Ensure all state events are in the timeline - // State.Where(s => !Timeline.Contains(s)).ToList().ForEach(s => Timeline.Add(s)); - // var path = Path.Combine(HSEConfiguration.Current.DataStoragePath, "rooms", $"{RoomId}.json"); - // Console.WriteLine($"Saving room {RoomId} to {path}!"); - // await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true)); - // } - // catch (TaskCanceledException) { } + // if (!HSEConfiguration.Current.StoreData) return; + // await _debounceCts.CancelAsync(); + // _debounceCts = new CancellationTokenSource(); + // try { + // await Task.Delay(250, _debounceCts.Token); + // // Ensure all state events are in the timeline + // State.Where(s => !Timeline.Contains(s)).ToList().ForEach(s => Timeline.Add(s)); + // var path = Path.Combine(HSEConfiguration.Current.DataStoragePath, "rooms", $"{RoomId}.json"); + // Console.WriteLine($"Saving room {RoomId} to {path}!"); + // await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true)); + // } + // catch (TaskCanceledException) { } // } private SemaphoreSlim saveSemaphore = new(1, 1); + private CancellationTokenSource _saveCts = new(); + public async Task SaveDebounced() { Task.Run(async () => { - await saveSemaphore.WaitAsync(); + // await saveSemaphore.WaitAsync(); + await _saveCts.CancelAsync(); + _saveCts = new(); try { - var path = Path.Combine(HSEConfiguration.Current.DataStoragePath, "rooms", $"{RoomId}.json"); - Console.WriteLine($"Saving room {RoomId} to {path}!"); - await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true)); + var path = Path.Combine(HseConfiguration.Current.DataStoragePath, "rooms", $"{RoomId}.json"); + // Console.WriteLine($"Saving room {RoomId} to {path}!"); + await File.WriteAllTextAsync(path, this.ToJson(ignoreNull: true), _saveCts.Token); } finally { - saveSemaphore.Release(); + // saveSemaphore.Release(); } }); } diff --git a/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs b/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs
index 4684b01..7f211e3 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Services/UserStore.cs
@@ -5,7 +5,6 @@ using System.Text.Json.Nodes; using ArcaneLibs; using ArcaneLibs.Collections; using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.Filters; using LibMatrix.Responses; @@ -13,14 +12,14 @@ namespace LibMatrix.HomeserverEmulator.Services; public class UserStore { public ConcurrentBag<User> _users = new(); - private readonly HSEConfiguration _config; + private readonly HseConfiguration _config; private readonly RoomStore _roomStore; - public UserStore(HSEConfiguration config, RoomStore roomStore) { + public UserStore(HseConfiguration config, RoomStore roomStore) { _config = config; _roomStore = roomStore; if (config.StoreData) { - var dataDir = Path.Combine(HSEConfiguration.Current.DataStoragePath, "users"); + var dataDir = Path.Combine(HseConfiguration.Current.DataStoragePath, "users"); if (!Directory.Exists(dataDir)) Directory.CreateDirectory(dataDir); foreach (var userId in Directory.GetDirectories(dataDir)) { var tokensDir = Path.Combine(dataDir, userId, "tokens.json"); @@ -138,7 +137,7 @@ public class UserStore { private ObservableDictionary<string, object> _profile; private ObservableCollection<StateEventResponse> _accountData; private ObservableDictionary<string, RoomKeysResponse> _roomKeys; - private ObservableDictionary<string, AuthorizedSession> _authorizedSessions; + private ObservableDictionary<string, LoginResponse> _authorizedSessions; public string UserId { get => _userId; @@ -195,7 +194,7 @@ public class UserStore { } } - public ObservableDictionary<string, AuthorizedSession> AuthorizedSessions { + public ObservableDictionary<string, LoginResponse> AuthorizedSessions { get => _authorizedSessions; set { if (value == _authorizedSessions) return; @@ -208,12 +207,12 @@ public class UserStore { public bool IsGuest { get; set; } public async Task SaveDebounced() { - if (!HSEConfiguration.Current.StoreData) return; + if (!HseConfiguration.Current.StoreData) return; await _debounceCts.CancelAsync(); _debounceCts = new CancellationTokenSource(); try { await Task.Delay(250, _debounceCts.Token); - var dataDir = Path.Combine(HSEConfiguration.Current.DataStoragePath, "users", _userId); + var dataDir = Path.Combine(HseConfiguration.Current.DataStoragePath, "users", _userId); if (!Directory.Exists(dataDir)) Directory.CreateDirectory(dataDir); var tokensDir = Path.Combine(dataDir, "tokens.json"); var path = Path.Combine(dataDir, $"user.json"); @@ -264,10 +263,5 @@ public class UserStore { UserId = UserId }; } - - public class AuthorizedSession { - public string Homeserver { get; set; } - public string AccessToken { get; set; } - } } } \ No newline at end of file diff --git a/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj b/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj
index 879693e..b054aba 100644 --- a/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj +++ b/Utilities/LibMatrix.TestDataGenerator/LibMatrix.TestDataGenerator.csproj
@@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net8.0</TargetFramework> + <TargetFramework>net9.0</TargetFramework> <LangVersion>preview</LangVersion> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> @@ -17,7 +17,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0"/> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1"/> </ItemGroup> <ItemGroup> <Content Include="appsettings*.json"> @@ -27,6 +27,6 @@ <ItemGroup> <ProjectReference Include="..\..\LibMatrix\LibMatrix.csproj"/> <ProjectReference Include="..\..\Utilities\LibMatrix.Utilities.Bot\LibMatrix.Utilities.Bot.csproj"/> - <ProjectReference Include="..\LibMatrix.Tests\LibMatrix.Tests.csproj"/> + <ProjectReference Include="..\..\Tests\LibMatrix.Tests\LibMatrix.Tests.csproj"/> </ItemGroup> </Project> diff --git a/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs
index 2cfcf32..6dc76f6 100644 --- a/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs +++ b/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs
@@ -4,28 +4,28 @@ namespace LibMatrix.Utilities.Bot.AppServices; public class AppServiceConfiguration { [JsonPropertyName("id")] - public string Id { get; set; } = null!; + public string Id { get; set; } [JsonPropertyName("url")] - public string? Url { get; set; } = null!; + public string? Url { get; set; } [JsonPropertyName("sender_localpart")] - public string SenderLocalpart { get; set; } = null!; + public string SenderLocalpart { get; set; } [JsonPropertyName("as_token")] - public string AppserviceToken { get; set; } = null!; + public string AppserviceToken { get; set; } [JsonPropertyName("hs_token")] - public string HomeserverToken { get; set; } = null!; + public string HomeserverToken { get; set; } [JsonPropertyName("protocols")] - public List<string>? Protocols { get; set; } = null!; + public List<string>? Protocols { get; set; } [JsonPropertyName("rate_limited")] - public bool? RateLimited { get; set; } = null!; + public bool? RateLimited { get; set; } [JsonPropertyName("namespaces")] - public AppserviceNamespaces Namespaces { get; set; } = null!; + public AppserviceNamespaces Namespaces { get; set; } public class AppserviceNamespaces { [JsonPropertyName("users")] @@ -42,7 +42,7 @@ public class AppServiceConfiguration { public bool Exclusive { get; set; } [JsonPropertyName("regex")] - public string Regex { get; set; } = null!; + public string Regex { get; set; } } } diff --git a/Utilities/LibMatrix.Utilities.Bot/Commands/AliassesCommand.cs b/Utilities/LibMatrix.Utilities.Bot/Commands/AliassesCommand.cs
index 5c9c480..9107c4c 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Commands/AliassesCommand.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Commands/AliassesCommand.cs
@@ -1,4 +1,3 @@ -using System.Collections.Frozen; using System.Text; using LibMatrix.EventTypes.Spec; using LibMatrix.Helpers; diff --git a/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs b/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs
index 0abc76b..d55c67c 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Commands/HelpCommand.cs
@@ -1,6 +1,3 @@ -using System.Collections.Frozen; -using System.Text; -using LibMatrix.EventTypes.Spec; using LibMatrix.Helpers; using LibMatrix.Utilities.Bot.Interfaces; using Microsoft.Extensions.DependencyInjection; diff --git a/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs b/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs
index 941d69e..7b4afa9 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Interfaces/ICommand.cs
@@ -1,7 +1,3 @@ -using System.Collections.Frozen; -using System.Collections.Immutable; -using Microsoft.Extensions.DependencyInjection; - namespace LibMatrix.Utilities.Bot.Interfaces; public interface ICommand { diff --git a/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj b/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj
index 0d0c2ae..bbb0a65 100644 --- a/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj +++ b/Utilities/LibMatrix.Utilities.Bot/LibMatrix.Utilities.Bot.csproj
@@ -12,9 +12,9 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" /> - <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" /> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" /> + <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" /> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" /> </ItemGroup> diff --git a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs
index 728b169..da83cfa 100644 --- a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs +++ b/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs
@@ -1,4 +1,3 @@ -using LibMatrix.Utilities.Bot.Interfaces; using Microsoft.Extensions.Configuration; namespace LibMatrix.Utilities.Bot; diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
index 9a7585e..d07090f 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Services/CommandListenerHostedService.cs
@@ -1,7 +1,6 @@ -using System.Reflection.Metadata; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers; diff --git a/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs b/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs
index 7c5cc44..88a6a03 100644 --- a/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs +++ b/Utilities/LibMatrix.Utilities.Bot/Services/InviteListenerHostedService.cs
@@ -1,12 +1,6 @@ -using System.Reflection.Metadata; -using ArcaneLibs.Extensions; -using LibMatrix.EventTypes.Spec; using LibMatrix.Filters; using LibMatrix.Helpers; using LibMatrix.Homeservers; -using LibMatrix.Responses; -using LibMatrix.Utilities.Bot.Interfaces; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging;