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; }
|