about summary refs log tree commit diff
path: root/LibMatrix/Helpers
diff options
context:
space:
mode:
Diffstat (limited to 'LibMatrix/Helpers')
-rw-r--r--LibMatrix/Helpers/MessageBuilder.cs18
-rw-r--r--LibMatrix/Helpers/MessageFormatter.cs7
-rw-r--r--LibMatrix/Helpers/RoomBuilder.cs142
-rw-r--r--LibMatrix/Helpers/RoomUpgradeBuilder.cs232
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs10
-rw-r--r--LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs14
-rw-r--r--LibMatrix/Helpers/SyncStateResolver.cs2
7 files changed, 390 insertions, 35 deletions
diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs

index 6f55739..f753bf7 100644 --- a/LibMatrix/Helpers/MessageBuilder.cs +++ b/LibMatrix/Helpers/MessageBuilder.cs
@@ -95,8 +95,13 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } - public MessageBuilder WithMention(string id, string? displayName = null) { - Content.Body += $"@{displayName ?? id}"; + public MessageBuilder WithMention(string id, string? displayName = null, string[]? vias = null, bool useIdInPlainText = false, bool useLinkInPlainText = false) { + if (!useLinkInPlainText) Content.Body += $"@{(useIdInPlainText ? id : displayName ?? id)}"; + else { + Content.Body += $"https://matrix.to/#/{id}"; + if (vias is { Length: > 0 }) Content.Body += $"?via={string.Join("&via=", vias)}"; + } + Content.FormattedBody += $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>"; if (id == "@room") { Content.Mentions ??= new(); @@ -111,6 +116,15 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } + public MessageBuilder WithRoomMention() { + // Legacy push rules support + Content.Body += "@room"; + Content.FormattedBody += "@room"; + Content.Mentions ??= new(); + Content.Mentions.Room = true; + return this; + } + public MessageBuilder WithNewline() { Content.Body += "\n"; Content.FormattedBody += "<br>"; diff --git a/LibMatrix/Helpers/MessageFormatter.cs b/LibMatrix/Helpers/MessageFormatter.cs
index 1b9b4f3..780ac0e 100644 --- a/LibMatrix/Helpers/MessageFormatter.cs +++ b/LibMatrix/Helpers/MessageFormatter.cs
@@ -30,8 +30,11 @@ public static class MessageFormatter { public static string HtmlFormatMention(string id, string? displayName = null) => $"<a href=\"https://matrix.to/#/{id}\">{displayName ?? id}</a>"; - public static string HtmlFormatMessageLink(string roomId, string eventId, string[]? servers = null, string? displayName = null) { - if (servers is not { Length: > 0 }) servers = new[] { roomId.Split(':', 2)[1] }; + public static string HtmlFormatMessageLink(string roomId, string eventId, string[] servers, string? displayName = null) { + if (servers is not { Length: > 0 }) + servers = roomId.Contains(':') + ? [roomId.Split(':', 2)[1]] + : throw new ArgumentException("Message links must contain a list of via's for v12+ rooms!", nameof(servers)); return $"<a href=\"https://matrix.to/#/{roomId}/{eventId}?via={string.Join("&via=", servers)}\">{displayName ?? eventId}</a>"; } diff --git a/LibMatrix/Helpers/RoomBuilder.cs b/LibMatrix/Helpers/RoomBuilder.cs
index bef7568..a292f33 100644 --- a/LibMatrix/Helpers/RoomBuilder.cs +++ b/LibMatrix/Helpers/RoomBuilder.cs
@@ -1,14 +1,20 @@ +using System.Diagnostics; using System.Runtime.Intrinsics.X86; +using System.Text.RegularExpressions; +using ArcaneLibs.Extensions; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; using LibMatrix.Responses; using LibMatrix.RoomTypes; +using LibMatrix.StructuredData; namespace LibMatrix.Helpers; 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(); @@ -25,22 +31,37 @@ public class RoomBuilder { HistoryVisibility = RoomHistoryVisibilityEventContent.HistoryVisibilityTypes.Shared }; + public RoomGuestAccessEventContent GuestAccess { get; set; } = new() { + GuestAccess = "forbidden" + }; + + public RoomServerAclEventContent ServerAcls { get; set; } = new() { + AllowIpLiterals = false + }; + + public RoomEncryptionEventContent Encryption { get; set; } = new(); + /// <summary> /// State events to be sent *before* room access is configured. Keep this small! /// </summary> - public List<StateEvent> ImportantState { get; set; } = []; + public List<MatrixEvent> ImportantState { get; set; } = []; /// <summary> /// State events to be sent *after* room access is configured, but before invites are sent. /// </summary> - public List<StateEvent> InitialState { get; set; } = []; + public List<MatrixEvent> InitialState { get; set; } = []; /// <summary> /// Users to invite, with optional reason /// </summary> - public Dictionary<string, string?> Invites { get; set; } = new(); + public Dictionary<string, string?> Invites { get; set; } = []; - public RoomPowerLevelEventContent PowerLevels { get; init; } = new() { + /// <summary> + /// Users to ban, with optional reason + /// </summary> + public Dictionary<string, string?> Bans { get; set; } = []; + + public RoomPowerLevelEventContent PowerLevels { get; set; } = new() { EventsDefault = 0, UsersDefault = 0, Kick = 50, @@ -57,16 +78,28 @@ public class RoomBuilder { { RoomCanonicalAliasEventContent.EventId, 50 }, { RoomEncryptionEventContent.EventId, 100 }, { RoomHistoryVisibilityEventContent.EventId, 100 }, + { RoomGuestAccessEventContent.EventId, 100 }, { RoomNameEventContent.EventId, 50 }, { RoomPowerLevelEventContent.EventId, 100 }, { RoomServerAclEventContent.EventId, 100 }, - { RoomTombstoneEventContent.EventId, 100 }, - { RoomPolicyServerEventContent.EventId, 100 } + { RoomTombstoneEventContent.EventId, 150 }, + { RoomPolicyServerEventContent.EventId, 100 }, + { RoomPinnedEventContent.EventId, 50 }, + // recommended extensions + { "im.vector.modular.widgets", 50 }, + // { "m.reaction", 0 }, // we probably don't want these to end up as room state + // - prevent calls + { "io.element.voice_broadcast_info", 50 }, + { "org.matrix.msc3401.call", 50 }, + { "org.matrix.msc3401.call.member", 50 }, } }; - public async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { - var crq = new CreateRoomRequest() { + public Dictionary<string, object> AdditionalCreationContent { get; set; } = new(); + public List<string> AdditionalCreators { get; set; } = new(); + + public virtual async Task<GenericRoom> Create(AuthenticatedHomeserverGeneric homeserver) { + var crq = new CreateRoomRequest { PowerLevelContentOverride = new() { EventsDefault = 1000000, UsersDefault = 1000000, @@ -78,7 +111,7 @@ public class RoomBuilder { NotificationsPl = new() { Room = 1000000 }, - Users = new Dictionary<string, long>() { + Users = new() { { homeserver.WhoAmI.UserId, MatrixConstants.MaxSafeJsonInteger } }, Events = new Dictionary<string, long> { @@ -86,12 +119,13 @@ public class RoomBuilder { { RoomCanonicalAliasEventContent.EventId, 1000000 }, { RoomEncryptionEventContent.EventId, 1000000 }, { RoomHistoryVisibilityEventContent.EventId, 1000000 }, + { RoomGuestAccessEventContent.EventId, 1000000 }, { RoomNameEventContent.EventId, 1000000 }, { RoomPowerLevelEventContent.EventId, 1000000 }, { RoomServerAclEventContent.EventId, 1000000 }, { RoomTombstoneEventContent.EventId, 1000000 }, { RoomPolicyServerEventContent.EventId, 1000000 } - } + }, }, Visibility = "private", RoomVersion = Version @@ -103,8 +137,25 @@ public class RoomBuilder { if (!IsFederatable) crq.CreationContent.Add("m.federate", false); + AdditionalCreators.RemoveAll(string.IsNullOrWhiteSpace); + 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) { + crq.CreationContent.Add(kvp.Key, kvp.Value); + } + 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); @@ -117,6 +168,23 @@ public class RoomBuilder { private async Task SendInvites(GenericRoom room) { if (Invites.Count == 0) return; + if (SynapseAdminAutoAcceptLocalInvites && room.Homeserver is AuthenticatedHomeserverSynapse synapse) { + var localJoinTasks = Invites.Where(u => UserId.Parse(u.Key).ServerName == synapse.ServerName).Select(async entry => { + var user = entry.Key; + var reason = entry.Value; + try { + var uhs = await synapse.Admin.GetHomeserverForUserAsync(user, TimeSpan.FromHours(1)); + var userRoom = uhs.GetRoom(room.RoomId); + await userRoom.JoinAsync([uhs.ServerName], reason); + await uhs.Logout(); + } + catch (MatrixException e) { + Console.WriteLine("Failed to auto-accept invite for {0} in {1}: {2}", user, room.RoomId, e.Message); + } + }).ToList(); + await Task.WhenAll(localJoinTasks); + } + var inviteTasks = Invites.Select(async kvp => { try { await room.InviteUserAsync(kvp.Key, kvp.Value); @@ -129,12 +197,39 @@ public class RoomBuilder { await Task.WhenAll(inviteTasks); } - 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)); - } + private async Task SetStatesAsync(GenericRoom room, List<MatrixEvent> state) { + 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}."); + // // } + // } } private async Task SetBasicRoomInfoAsync(GenericRoom room) { @@ -154,10 +249,21 @@ public class RoomBuilder { await room.Homeserver.SetRoomAliasAsync(CanonicalAlias.Alias!, room.RoomId); await room.SendStateEventAsync(RoomCanonicalAliasEventContent.EventId, CanonicalAlias); } + + if (!string.IsNullOrWhiteSpace(Encryption.Algorithm)) + await room.SendStateEventAsync(RoomEncryptionEventContent.EventId, Encryption); } private async Task SetAccessAsync(GenericRoom room) { - PowerLevels.Users![room.Homeserver.WhoAmI.UserId] = OwnPowerLevel; + if (!V12PlusRoomVersions.Contains(Version)) + PowerLevels.Users![room.Homeserver.WhoAmI.UserId] = OwnPowerLevel; + else { + PowerLevels.Users!.Remove(room.Homeserver.WhoAmI.UserId); + foreach (var additionalCreator in AdditionalCreators) { + PowerLevels.Users!.Remove(additionalCreator); + } + } + await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, PowerLevels); if (!string.IsNullOrWhiteSpace(HistoryVisibility.HistoryVisibility)) diff --git a/LibMatrix/Helpers/RoomUpgradeBuilder.cs b/LibMatrix/Helpers/RoomUpgradeBuilder.cs new file mode 100644
index 0000000..ced0ef3 --- /dev/null +++ b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
@@ -0,0 +1,232 @@ +using System.Diagnostics; +using System.Reflection; +using System.Text.Json.Serialization; +using ArcaneLibs; +using LibMatrix.EventTypes; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State.Policy; +using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Homeservers; +using LibMatrix.RoomTypes; +using LibMatrix.StructuredData; + +namespace LibMatrix.Helpers; + +public class RoomUpgradeBuilder : RoomBuilder { + public RoomUpgradeOptions UpgradeOptions { get; set; } = new(); + public string OldRoomId { get; set; } = string.Empty; + public bool CanUpgrade { get; private set; } + public Dictionary<string, object> AdditionalTombstoneContent { get; set; } = new(); + + private List<Type> basePolicyTypes = []; + + public async Task ImportAsync(GenericRoom OldRoom) { + var sw = Stopwatch.StartNew(); + var total = 0; + + basePolicyTypes = ClassCollector<PolicyRuleEventContent>.ResolveFromAllAccessibleAssemblies().ToList(); + Console.WriteLine($"Found {basePolicyTypes.Count} policy types in {sw.ElapsedMilliseconds}ms"); + CanUpgrade = ( + (await OldRoom.GetPowerLevelsAsync())?.UserHasStatePermission(OldRoom.Homeserver.UserId, RoomTombstoneEventContent.EventId) + ?? (await OldRoom.GetRoomCreatorsAsync()).Contains(OldRoom.Homeserver.UserId) + ) + || (OldRoom.IsV12PlusRoomId && (await OldRoom.GetRoomCreatorsAsync()).Contains(OldRoom.Homeserver.UserId)); + + await foreach (var srcEvt in OldRoom.GetFullStateAsync()) { + total++; + if (srcEvt is null) continue; + var evt = srcEvt; + + if (UpgradeOptions.UpgradeUnstableValues) { + evt = UpgradeUnstableValues(evt); + } + + if (evt.StateKey == "") { + if (evt.Type == RoomCreateEventContent.EventId) + foreach (var (key, value) in evt.RawContent) { + if (key is "room_version" or "creator") continue; + if (key == "type") + Type = value!.GetValue<string>(); + else AdditionalCreationContent[key] = value; + } + else if (evt.Type == RoomNameEventContent.EventId) + Name = evt.ContentAs<RoomNameEventContent>()!; + else if (evt.Type == RoomTopicEventContent.EventId) + Topic = evt.ContentAs<RoomTopicEventContent>()!; + else if (evt.Type == RoomAvatarEventContent.EventId) + Avatar = evt.ContentAs<RoomAvatarEventContent>()!; + else if (evt.Type == RoomCanonicalAliasEventContent.EventId) { + CanonicalAlias = evt.ContentAs<RoomCanonicalAliasEventContent>()!; + AliasLocalPart = CanonicalAlias.Alias?.Split(':', 2).FirstOrDefault()?[1..] ?? string.Empty; + } + else if (evt.Type == RoomJoinRulesEventContent.EventId) + JoinRules = evt.ContentAs<RoomJoinRulesEventContent>()!; + else if (evt.Type == RoomHistoryVisibilityEventContent.EventId) + HistoryVisibility = evt.ContentAs<RoomHistoryVisibilityEventContent>()!; + else if (evt.Type == RoomGuestAccessEventContent.EventId) + GuestAccess = evt.ContentAs<RoomGuestAccessEventContent>()!; + else if (evt.Type == RoomServerAclEventContent.EventId) + ServerAcls = evt.ContentAs<RoomServerAclEventContent>()!; + else if (evt.Type == RoomPowerLevelEventContent.EventId) { + PowerLevels = evt.ContentAs<RoomPowerLevelEventContent>()!; + if (UpgradeOptions.InvitePowerlevelUsers && PowerLevels.Users != null) + foreach (var (userId, level) in PowerLevels.Users) + if (level > PowerLevels.UsersDefault) + Invites.Add(userId, "Room upgrade (had a power level)"); + } + else if (evt.Type == RoomEncryptionEventContent.EventId) + Encryption = evt.ContentAs<RoomEncryptionEventContent>(); + else if (evt.Type == RoomPinnedEventContent.EventId) ; // Discard as you can't cross reference pinned events + else + InitialState.Add(new() { + Type = evt.Type, + StateKey = evt.StateKey, + RawContent = evt.RawContent + }); + } + else if (evt.Type == RoomMemberEventContent.EventId) { + if (evt.TypedContent is RoomMemberEventContent { Membership: "join" or "invite" } invitedMember) { + if (UpgradeOptions.InviteMembers) + Invites.TryAdd(evt.StateKey!, invitedMember.Reason ?? "Room upgrade"); + else if (UpgradeOptions.InviteLocalMembers && UserId.Parse(evt.StateKey!).ServerName == OldRoom.Homeserver.ServerName) + Invites.TryAdd(evt.StateKey!, invitedMember.Reason ?? "Room upgrade (local user)"); + } + else if (UpgradeOptions.MigrateBans && evt.TypedContent is RoomMemberEventContent { Membership: "ban" } bannedMember) + Bans.TryAdd(evt.StateKey!, bannedMember.Reason); + } + else if (!UpgradeOptions.MigrateEmptyStateEvents && evt.RawContent.Count == 0) { } // skip empty state events + else if (basePolicyTypes.Contains(evt.MappedType)) ImportPolicyEventAsync(evt); + else + InitialState.Add(new() { + Type = evt.Type, + StateKey = evt.StateKey, + RawContent = evt.RawContent + }); + } + + Console.WriteLine($"Imported {total} state events from old room {OldRoom.RoomId} in {sw.ElapsedMilliseconds}ms"); + } + + private MatrixEventResponse UpgradeUnstableValues(MatrixEventResponse evt) { + if (evt.IsLegacyType) { + var oldType = evt.Type; + evt.Type = evt.MappedType.GetCustomAttributes<MatrixEventAttribute>().FirstOrDefault(x => !x.Legacy)!.EventName; + Console.WriteLine($"Upgraded event type from {oldType} to {evt.Type} for event {evt.EventId}"); + } + + if (evt.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) { + if (evt.RawContent["recommendation"]?.GetValue<string>() == "org.matrix.mjolnir.ban") { + evt.RawContent["recommendation"] = "m.ban"; + Console.WriteLine($"Upgraded recommendation from 'org.matrix.mjolnir.ban' to 'm.ban' for event {evt.EventId}"); + } + } + + return evt; + } + + private void ImportPolicyEventAsync(MatrixEventResponse evt) { + var msc4321Options = UpgradeOptions.Msc4321PolicyListUpgradeOptions; + if (msc4321Options is { Enable: true, UpgradeType: Msc4321PolicyListUpgradeOptions.Msc4321PolicyListUpgradeType.Transition }) + return; // this upgrade type doesnt copy policies + if (msc4321Options.Enable) { + evt.RawContent["org.matrix.msc4321.original_sender"] = evt.Sender; + evt.RawContent["org.matrix.msc4321.original_timestamp"] = evt.OriginServerTs; + evt.RawContent["org.matrix.msc4321.original_event_id"] = evt.EventId; + } + + InitialState.Add(new() { + Type = evt.Type, + StateKey = evt.StateKey, + RawContent = evt.RawContent + }); + } + + 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}"); + if (aliasResult?.RoomId == OldRoomId) + await homeserver.DeleteRoomAliasAsync($"#{AliasLocalPart}:{homeserver.ServerName}"); + else + throw new LibMatrixException() { + ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED, + Error = $"Cannot upgrade room {OldRoomId} as it has an alias that is not the same as the one tracked by the server! Server says: {aliasResult.RoomId}" + }; + } + + var room = await base.Create(homeserver); + if (CanUpgrade || UpgradeOptions.ForceUpgrade) { + if (UpgradeOptions.RoomUpgradeNotice != null) { + var noticeContent = await UpgradeOptions.RoomUpgradeNotice(room); + await oldRoom.SendMessageEventAsync(noticeContent); + } + + var tombstoneContent = new RoomTombstoneEventContent { + Body = "This room has been upgraded to a new version.", + ReplacementRoom = room.RoomId + }; + + tombstoneContent.AdditionalData ??= []; + foreach (var (key, value) in AdditionalTombstoneContent) + tombstoneContent.AdditionalData[key] = value; + + await oldRoom.SendStateEventAsync(RoomTombstoneEventContent.EventId, tombstoneContent); + } + + return room; + } + + public class RoomUpgradeOptions { + public bool InviteMembers { get; set; } + public bool InviteLocalMembers { get; set; } + public bool InvitePowerlevelUsers { get; set; } + public bool MigrateBans { get; set; } + 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] + public Func<GenericRoom, Task<RoomMessageEventContent>>? RoomUpgradeNotice { get; set; } = async newRoom => new MessageBuilder() + .WithRoomMention() + .WithNewline() + .WithBody("This room has been upgraded to a new version. This version of the room will be kept as an archive.") + .WithNewline() + .WithBody("You can join the new room by clicking the link below:") + .WithNewline() + .WithMention(newRoom.RoomId, await newRoom.GetNameOrFallbackAsync(), vias: (await newRoom.GetHomeserversInRoom()).ToArray(), useLinkInPlainText: true) + .Build(); + } + + public class Msc4321PolicyListUpgradeOptions { + public bool Enable { get; set; } = true; + public Msc4321PolicyListUpgradeType UpgradeType { get; set; } = Msc4321PolicyListUpgradeType.Move; + + [JsonConverter(typeof(JsonStringEnumConverter))] + public enum Msc4321PolicyListUpgradeType { + /// <summary> + /// Copy policies, unwatch old list + /// </summary> + Move, + + /// <summary> + /// Don't copy policies, watch both lists + /// </summary> + Transition + } + } +} \ No newline at end of file diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index c8e2928..ebe653c 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -298,9 +298,9 @@ 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 ?? []) { - stateEventResponse.RoomId = updatedRoom.Key; - var tasks = TimelineEventHandlers.Select(x => x(stateEventResponse)).ToList(); + foreach (var MatrixEventResponse in updatedRoom.Value.Timeline.Events ?? []) { + MatrixEventResponse.RoomId = updatedRoom.Key; + var tasks = TimelineEventHandlers.Select(x => x(MatrixEventResponse)).ToList(); await Task.WhenAll(tasks); } } @@ -319,12 +319,12 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg /// <summary> /// Event fired when a timeline event is received /// </summary> - public List<Func<StateEventResponse, Task>> TimelineEventHandlers { get; } = new(); + public List<Func<MatrixEventResponse, Task>> TimelineEventHandlers { get; } = new(); /// <summary> /// Event fired when an account data event is received /// </summary> - public List<Func<StateEventResponse, Task>> AccountDataReceivedHandlers { get; } = new(); + public List<Func<MatrixEventResponse, Task>> AccountDataReceivedHandlers { get; } = new(); /// <summary> /// Event fired when an exception is thrown diff --git a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
index e34b5cf..c887f6e 100644 --- a/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs +++ b/LibMatrix/Helpers/SyncProcessors/Msc4222EmulationSyncProcessor.cs
@@ -8,11 +8,11 @@ using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers.SyncProcessors; public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homeserver, ILogger? logger) { - private static bool StateEventsMatch(StateEventResponse a, StateEventResponse b) { + private static bool StateEventsMatch(MatrixEventResponse a, MatrixEventResponse b) { return a.Type == b.Type && a.StateKey == b.StateKey; } - private static bool StateEventIsNewer(StateEventResponse a, StateEventResponse b) { + private static bool StateEventIsNewer(MatrixEventResponse a, MatrixEventResponse b) { return StateEventsMatch(a, b) && a.OriginServerTs < b.OriginServerTs; } @@ -45,7 +45,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese tasks.AddRange(resp.Rooms.Leave.Select(ProcessLeftRooms).ToList()); } - var tasksEnum = tasks.ToAsyncEnumerable(); + var tasksEnum = tasks.ToAsyncResultEnumerable(); await foreach (var wasModified in tasksEnum) { if (wasModified) { modified = true; @@ -76,7 +76,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese Events = [] }; - var oldState = new List<StateEventResponse>(); + var oldState = new List<MatrixEventResponse>(); if (data.State is { Events.Count: > 0 }) { oldState.ReplaceBy(data.State.Events, StateEventIsNewer); } @@ -129,7 +129,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } }); - var tasksEnum = tasks.ToAsyncEnumerable(); + var tasksEnum = tasks.ToAsyncResultEnumerable(); await foreach (var evt in tasksEnum) { data.StateAfter.Events.Add(evt); } @@ -160,7 +160,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese logger?.LogWarning("Msc4222Emulation: Failed to get full state for room {roomId}, state may be incomplete!\n{exception}", roomId, e); } - var oldState = new List<StateEventResponse>(); + var oldState = new List<MatrixEventResponse>(); if (data.State is { Events.Count: > 0 }) { oldState.ReplaceBy(data.State.Events, StateEventIsNewer); } @@ -198,7 +198,7 @@ public class Msc4222EmulationSyncProcessor(AuthenticatedHomeserverGeneric homese } }); - var tasksEnum = tasks.ToAsyncEnumerable(); + var tasksEnum = tasks.ToAsyncResultEnumerable(); await foreach (var evt in tasksEnum) { data.StateAfter.Events.Add(evt); } diff --git a/LibMatrix/Helpers/SyncStateResolver.cs b/LibMatrix/Helpers/SyncStateResolver.cs
index f111c79..17c1a41 100644 --- a/LibMatrix/Helpers/SyncStateResolver.cs +++ b/LibMatrix/Helpers/SyncStateResolver.cs
@@ -625,7 +625,7 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge return oldState; } - private static EventList? MergeEventListBy(EventList? oldState, EventList? newState, Func<StateEventResponse, StateEventResponse, bool> comparer) { + private static EventList? MergeEventListBy(EventList? oldState, EventList? newState, Func<MatrixEventResponse, MatrixEventResponse, bool> comparer) { if (newState is null) return oldState; if (oldState is null) { return newState;