about summary refs log tree commit diff
diff options
context:
space:
mode:
m---------ArcaneLibs0
-rw-r--r--LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs18
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Single.cs9
-rw-r--r--LibMatrix/Helpers/RoomBuilder.cs71
-rw-r--r--LibMatrix/Helpers/RoomUpgradeBuilder.cs15
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs20
-rw-r--r--LibMatrix/StateEvent.cs8
7 files changed, 96 insertions, 45 deletions
diff --git a/ArcaneLibs b/ArcaneLibs
-Subproject 57ca515a61ae4f428981e2a548c733095a9f010
+Subproject f0f37be308ce87f55c7299f08c8ce5077b057c8
diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs

index 24b8f90..36c94ae 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -1,5 +1,6 @@ using System.Diagnostics; using System.Security.Cryptography; +using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using ArcaneLibs.Attributes; @@ -87,20 +88,16 @@ public abstract class PolicyRuleEventContent : EventContent { public PolicyHash? Hashes { get; set; } public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray())); - - public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); - - public bool IsGlobRule() => - !string.IsNullOrWhiteSpace(Entity) - && (Entity.Contains('*') || Entity.Contains('?')); + public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", "."), RegexOptions.Compiled); + public bool IsGlobRule() => !string.IsNullOrWhiteSpace(Entity) && (Entity.Contains('*') || Entity.Contains('?')); + public bool IsHashedRule() => string.IsNullOrWhiteSpace(Entity) && Hashes is not null; public bool EntityMatches(string entity) { if (string.IsNullOrWhiteSpace(entity)) return false; if (!string.IsNullOrWhiteSpace(Entity)) { // Check if entity is equal regardless of glob check - var match = Entity == entity - || (IsGlobRule() && GetEntityRegex()!.IsMatch(entity)); + var match = Entity == entity || (IsGlobRule() && GetEntityRegex()!.IsMatch(entity)); if (match) return match; } @@ -124,7 +121,7 @@ public abstract class PolicyRuleEventContent : EventContent { return Recommendation; } - + public string? GetSpecRecommendation() { if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban") return PolicyRecommendationTypes.Ban; @@ -159,6 +156,9 @@ public static class PolicyRecommendationTypes { public class PolicyHash { [JsonPropertyName("sha256")] public string? Sha256 { get; set; } + + [JsonExtensionData] + public Dictionary<string, object>? AdditionalProperties { get; set; } } // public class PolicySchemaDefinition { diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index 430a6e5..26fb31f 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -31,12 +31,13 @@ public class MatrixHttpClient { }; } catch (PlatformNotSupportedException e) { - Console.WriteLine("Failed to create HttpClient with connection pooling, continuing without connection pool!"); - Console.WriteLine("Original exception (safe to ignore!):"); - Console.WriteLine(e); + Console.WriteLine("HTTP connection pooling is not supported on this platform, continuing without connection pooling!"); + // Console.WriteLine("Original exception (safe to ignore!):"); + // Console.WriteLine(e); Client = new HttpClient { - DefaultRequestVersion = new Version(3, 0) + DefaultRequestVersion = new Version(3, 0), + Timeout = TimeSpan.FromDays(1) }; } catch (Exception e) { diff --git a/LibMatrix/Helpers/RoomBuilder.cs b/LibMatrix/Helpers/RoomBuilder.cs
index 6dfb056..0b0b5f8 100644 --- a/LibMatrix/Helpers/RoomBuilder.cs +++ b/LibMatrix/Helpers/RoomBuilder.cs
@@ -1,4 +1,6 @@ +using System.Diagnostics; using System.Runtime.Intrinsics.X86; +using System.Text.RegularExpressions; using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; @@ -12,7 +14,7 @@ public class RoomBuilder { private static readonly string[] V12PlusRoomVersions = ["org.matrix.hydra.11", "12"]; public bool SynapseAdminAutoAcceptLocalInvites { get; set; } public string? Type { get; set; } - public string Version { get; set; } = "11"; + public string Version { get; set; } = "12"; public RoomNameEventContent Name { get; set; } = new(); public RoomTopicEventContent Topic { get; set; } = new(); public RoomAvatarEventContent Avatar { get; set; } = new(); @@ -37,7 +39,7 @@ public class RoomBuilder { AllowIpLiterals = false }; - public RoomEncryptionEventContent Encryption { get; set; } = new() { }; + public RoomEncryptionEventContent Encryption { get; set; } = new(); /// <summary> /// State events to be sent *before* room access is configured. Keep this small! @@ -89,7 +91,7 @@ public class RoomBuilder { public List<string> AdditionalCreators { get; set; } = new(); public virtual async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { - var crq = new CreateRoomRequest() { + var crq = new CreateRoomRequest { PowerLevelContentOverride = new() { EventsDefault = 1000000, UsersDefault = 1000000, @@ -101,11 +103,9 @@ public class RoomBuilder { NotificationsPl = new() { Room = 1000000 }, - Users = V12PlusRoomVersions.Contains(Version) - ? [] - : new() { - { homeserver.WhoAmI.UserId, MatrixConstants.MaxSafeJsonInteger } - }, + Users = new() { + { homeserver.WhoAmI.UserId, MatrixConstants.MaxSafeJsonInteger } + }, Events = new Dictionary<string, long> { { RoomAvatarEventContent.EventId, 1000000 }, { RoomCanonicalAliasEventContent.EventId, 1000000 }, @@ -130,10 +130,14 @@ public class RoomBuilder { crq.CreationContent.Add("m.federate", false); AdditionalCreators.RemoveAll(string.IsNullOrWhiteSpace); - if (V12PlusRoomVersions.Contains(Version) && AdditionalCreators is { Count: > 0 }) { - crq.CreationContent.Add("additional_creators", AdditionalCreators); - foreach (var user in AdditionalCreators) - PowerLevels.Users?.Remove(user); + if (V12PlusRoomVersions.Contains(Version)) { + crq.PowerLevelContentOverride.Users.Remove(homeserver.WhoAmI.UserId); + PowerLevels.Users?.Remove(homeserver.WhoAmI.UserId); + if (AdditionalCreators is { Count: > 0 }) { + crq.CreationContent.Add("additional_creators", AdditionalCreators); + foreach (var user in AdditionalCreators) + PowerLevels.Users?.Remove(user); + } } foreach (var kvp in AdditionalCreationContent) { @@ -142,6 +146,8 @@ public class RoomBuilder { var room = await homeserver.CreateRoom(crq); + Console.WriteLine("Press any key to continue..."); + Console.ReadKey(true); await SetBasicRoomInfoAsync(room); await SetStatesAsync(room, ImportantState); await SetAccessAsync(room); @@ -184,17 +190,38 @@ public class RoomBuilder { } private async Task SetStatesAsync(GenericRoom room, List<StateEvent> state) { - // foreach (var ev in state) { - // await (string.IsNullOrWhiteSpace(ev.StateKey) - // ? room.SendStateEventAsync(ev.Type, ev.RawContent) - // : room.SendStateEventAsync(ev.Type, ev.StateKey, ev.RawContent)); + if (state.Count == 0) return; + await room.BulkSendEventsAsync(state); + // We chunk this up to try to avoid hitting reverse proxy timeouts + // foreach (var group in state.Chunk(chunkSize)) { + // var sw = Stopwatch.StartNew(); + // await room.BulkSendEventsAsync(group); + // if (sw.ElapsedMilliseconds > 5000) { + // chunkSize = Math.Max(chunkSize / 2, 1); + // Console.WriteLine($"Warning: Sending {group.Length} state events took {sw.ElapsedMilliseconds}ms, which is quite long. Reducing chunk size to {chunkSize}."); + // } + // } + // int chunkSize = 50; + // for (int i = 0; i < state.Count; i += chunkSize) { + // var chunk = state.Skip(i).Take(chunkSize).ToList(); + // if (chunk.Count == 0) continue; + // + // var sw = Stopwatch.StartNew(); + // await room.BulkSendEventsAsync(chunk, forceSyncInterval: chunk.Count + 1); + // Console.WriteLine($"Sent {chunk.Count} state events in {sw.ElapsedMilliseconds}ms. {state.Count - (i + chunk.Count)} remaining."); + // // if (sw.ElapsedMilliseconds > 45000) { + // // chunkSize = Math.Max(chunkSize / 3, 1); + // // Console.WriteLine($"Warning: Sending {chunk.Count} state events took {sw.ElapsedMilliseconds}ms, which is dangerously long. Reducing chunk size to {chunkSize}."); + // // } + // // else if (sw.ElapsedMilliseconds > 30000) { + // // chunkSize = Math.Max(chunkSize / 2, 1); + // // Console.WriteLine($"Warning: Sending {chunk.Count} state events took {sw.ElapsedMilliseconds}ms, which is quite long. Reducing chunk size to {chunkSize}."); + // // } + // // else if (sw.ElapsedMilliseconds < 10000) { + // // chunkSize = Math.Min((int)(chunkSize * 1.2), 1000); + // // Console.WriteLine($"Info: Sending {chunk.Count} state events took {sw.ElapsedMilliseconds}ms, increasing chunk size to {chunkSize}."); + // // } // } - - foreach (var group in state.Chunk(100)) - await room.BulkSendEventsAsync(group); - - // var tasks = state.Chunk(50).Select(room.BulkSendEventsAsync).ToList(); - // await Task.WhenAll(tasks); } private async Task SetBasicRoomInfoAsync(GenericRoom room) { diff --git a/LibMatrix/Helpers/RoomUpgradeBuilder.cs b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
index 8eaf9b8..85a5e36 100644 --- a/LibMatrix/Helpers/RoomUpgradeBuilder.cs +++ b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
@@ -143,6 +143,18 @@ public class RoomUpgradeBuilder : RoomBuilder { public override async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { var oldRoom = homeserver.GetRoom(OldRoomId); + // set the previous room relation + AdditionalCreationContent["predecessor"] = new { + room_id = OldRoomId, + // event_id = (await oldRoom.GetMessagesAsync(limit: 1)).Chunk.Last().EventId + }; + + if (UpgradeOptions.NoopUpgrade) { + AliasLocalPart = null; + CanonicalAlias = new(); + return await base.Create(homeserver); + } + // prepare old room first... if (!string.IsNullOrWhiteSpace(AliasLocalPart)) { var aliasResult = await homeserver.ResolveRoomAliasAsync($"#{AliasLocalPart}:{homeserver.ServerName}"); @@ -185,6 +197,7 @@ public class RoomUpgradeBuilder : RoomBuilder { public bool MigrateEmptyStateEvents { get; set; } public bool UpgradeUnstableValues { get; set; } public bool ForceUpgrade { get; set; } + public bool NoopUpgrade { get; set; } public Msc4321PolicyListUpgradeOptions Msc4321PolicyListUpgradeOptions { get; set; } = new(); [JsonIgnore] @@ -211,7 +224,7 @@ public class RoomUpgradeBuilder : RoomBuilder { Move, /// <summary> - /// Don't copy policies + /// Don't copy policies, watch both lists /// </summary> Transition } diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 9753176..0077acb 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -623,10 +623,12 @@ public class GenericRoom { } } - public async Task BulkSendEventsAsync(IEnumerable<StateEvent> events) { - if ((await Homeserver.GetCapabilitiesAsync()).Capabilities.BulkSendEvents?.Enabled == true) - await Homeserver.ClientHttpClient.PostAsJsonAsync( - $"/_matrix/client/unstable/gay.rory.bulk_send_events/rooms/{RoomId}/bulk_send_events?_libmatrix_txn_id={Guid.NewGuid()}", events); + public async Task BulkSendEventsAsync(IEnumerable<StateEvent> events, int? forceSyncInterval = null) { + if ((await Homeserver.GetCapabilitiesAsync()).Capabilities.BulkSendEvents?.Enabled == true) { + var uri = $"/_matrix/client/unstable/gay.rory.bulk_send_events/rooms/{RoomId}/bulk_send_events?_libmatrix_txn_id={Guid.NewGuid()}"; + if (forceSyncInterval is not null) uri += $"&force_sync_interval={forceSyncInterval}"; + await Homeserver.ClientHttpClient.PostAsJsonAsync(uri, events); + } else { Console.WriteLine("Homeserver does not support bulk sending events, falling back to individual sends."); foreach (var evt in events) @@ -638,10 +640,12 @@ public class GenericRoom { } } - public async Task BulkSendEventsAsync(IAsyncEnumerable<StateEvent> events) { - if ((await Homeserver.GetCapabilitiesAsync()).Capabilities.BulkSendEvents?.Enabled == true) - await Homeserver.ClientHttpClient.PostAsJsonAsync( - $"/_matrix/client/unstable/gay.rory.bulk_send_events/rooms/{RoomId}/bulk_send_events?_libmatrix_txn_id={Guid.NewGuid()}", events); + public async Task BulkSendEventsAsync(IAsyncEnumerable<StateEvent> events, int? forceSyncInterval = null) { + if ((await Homeserver.GetCapabilitiesAsync()).Capabilities.BulkSendEvents?.Enabled == true) { + var uri = $"/_matrix/client/unstable/gay.rory.bulk_send_events/rooms/{RoomId}/bulk_send_events?_libmatrix_txn_id={Guid.NewGuid()}"; + if (forceSyncInterval is not null) uri += $"&force_sync_interval={forceSyncInterval}"; + await Homeserver.ClientHttpClient.PostAsJsonAsync(uri, events); + } else { Console.WriteLine("Homeserver does not support bulk sending events, falling back to individual sends."); await foreach (var evt in events) diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs
index aa755ef..8455098 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs
@@ -123,7 +123,7 @@ public class StateEvent { public static bool TypeKeyPairMatches(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey; public static bool Equals(StateEventResponse x, StateEventResponse y) => x.Type == y.Type && x.StateKey == y.StateKey && x.EventId == y.EventId; - + /// <summary> /// Compares two state events for deep equality, including type, state key, and raw content. /// If you trust the server, use Equals instead, as that compares by event ID instead of raw content. @@ -138,6 +138,12 @@ public class StateEventResponse : StateEvent { [JsonPropertyName("origin_server_ts")] public long? OriginServerTs { get; set; } + [JsonIgnore] + public DateTime? OriginServerTimestamp { + get => OriginServerTs.HasValue ? DateTimeOffset.FromUnixTimeMilliseconds(OriginServerTs.Value).UtcDateTime : DateTime.MinValue; + set => OriginServerTs = value is null ? null : new DateTimeOffset(value.Value).ToUnixTimeMilliseconds(); + } + [JsonPropertyName("room_id")] public string? RoomId { get; set; }