about summary refs log tree commit diff
path: root/LibMatrix/Helpers/RoomUpgradeBuilder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'LibMatrix/Helpers/RoomUpgradeBuilder.cs')
-rw-r--r--LibMatrix/Helpers/RoomUpgradeBuilder.cs180
1 files changed, 141 insertions, 39 deletions
diff --git a/LibMatrix/Helpers/RoomUpgradeBuilder.cs b/LibMatrix/Helpers/RoomUpgradeBuilder.cs

index cbc7876..64ec364 100644 --- a/LibMatrix/Helpers/RoomUpgradeBuilder.cs +++ b/LibMatrix/Helpers/RoomUpgradeBuilder.cs
@@ -1,71 +1,155 @@ +using System.Diagnostics; using System.Text.Json.Serialization; +using ArcaneLibs; using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State.Policy; using LibMatrix.EventTypes.Spec.State.RoomInfo; +using LibMatrix.Homeservers; using LibMatrix.RoomTypes; namespace LibMatrix.Helpers; -public class RoomUpgradeBuilder(GenericRoom oldRoom) : RoomBuilder { - public GenericRoom OldRoom { get; } = oldRoom; +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(); + + public async Task ImportAsync(GenericRoom OldRoom) { + var sw = Stopwatch.StartNew(); + var total = 0; + + var 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); + } - public async Task ImportAsync() { - await foreach (var evt in OldRoom.GetFullStateAsync()) { - if (evt is null) continue; if (evt.StateKey == "") { - if (evt.TypedContent is RoomCreateEventContent createEvt) + if (evt.Type == RoomCreateEventContent.EventId) foreach (var (key, value) in evt.RawContent) { if (key == "version") continue; if (key == "type") Type = value!.GetValue<string>(); else AdditionalCreationContent[key] = value; } - else if (evt.TypedContent is RoomNameEventContent name) - Name = name; - else if (evt.TypedContent is RoomTopicEventContent topic) - Topic = topic; - else if (evt.TypedContent is RoomAvatarEventContent avatar) - Avatar = avatar; - else if (evt.TypedContent is RoomCanonicalAliasEventContent alias) { - CanonicalAlias = alias; - AliasLocalPart = alias.Alias?.Split(':',2).FirstOrDefault()?[1..] ?? string.Empty; + 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.TypedContent is RoomJoinRulesEventContent joinRules) - JoinRules = joinRules; - else if (evt.TypedContent is RoomHistoryVisibilityEventContent historyVisibility) - HistoryVisibility = historyVisibility; - else if (evt.TypedContent is RoomGuestAccessEventContent guestAccess) - GuestAccess = guestAccess; - else if (evt.TypedContent is RoomServerAclEventContent serverAcls) - ServerAcls = serverAcls; - else if (evt.TypedContent is RoomPowerLevelEventContent powerLevels) { - if (UpgradeOptions.InvitePowerlevelUsers && powerLevels.Users != null) - foreach (var (userId, level) in powerLevels.Users) - if (level > powerLevels.UsersDefault) + 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)"); - - PowerLevels = powerLevels; } - else if (evt.TypedContent is RoomEncryptionEventContent encryption) - Encryption = encryption; - else if (evt.TypedContent is RoomPinnedEventContent) ; // Discard as you can't cross reference pinned events - else InitialState.Add(evt); + 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 (UpgradeOptions.InviteMembers && evt.TypedContent is RoomMemberEventContent { Membership: "join" or "invite" }) - if (!Invites.ContainsKey(evt.StateKey)) - Invites.Add(evt.StateKey, "Room upgrade"); - else if (UpgradeOptions.MigrateBans && evt.TypedContent is RoomMemberEventContent { Membership: "ban" } bannedMember) - Bans.Add(evt.StateKey, bannedMember.Reason); + if (UpgradeOptions.InviteMembers && evt.TypedContent is RoomMemberEventContent { Membership: "join" or "invite" } invitedMember) { + Invites.TryAdd(evt.StateKey!, invitedMember.Reason ?? "Room upgrade"); + } + else if (UpgradeOptions.MigrateBans && evt.TypedContent is RoomMemberEventContent { Membership: "ban" } bannedMember) + Bans.TryAdd(evt.StateKey!, bannedMember.Reason); } - else InitialState.Add(evt); + 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 StateEventResponse UpgradeUnstableValues(StateEventResponse evt) { + + return evt; + } + + private void ImportPolicyEventAsync(StateEventResponse 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 room = await base.Create(homeserver); + if (CanUpgrade || UpgradeOptions.ForceUpgrade) { + if (UpgradeOptions.RoomUpgradeNotice != null) { + var noticeContent = await UpgradeOptions.RoomUpgradeNotice(room); + await room.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 room.SendStateEventAsync(RoomTombstoneEventContent.EventId, tombstoneContent); + } + return room; } public class RoomUpgradeOptions { public bool InviteMembers { 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 Msc4321PolicyListUpgradeOptions Msc4321PolicyListUpgradeOptions { get; set; } = new(); [JsonIgnore] public Func<GenericRoom, Task<RoomMessageEventContent>> RoomUpgradeNotice { get; set; } = async newRoom => new MessageBuilder() @@ -78,4 +162,22 @@ public class RoomUpgradeBuilder(GenericRoom oldRoom) : RoomBuilder { .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 + /// </summary> + Transition + } + } } \ No newline at end of file