about summary refs log tree commit diff
path: root/MatrixUtils.Web/Pages/Tools/Room/FixCanonicalParentSpace.razor
blob: 8222729bb850d628c049526dd63c1ab02facc071 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
@page "/Tools/Room/FixCanonicalParentSpace"
@using System.Collections.ObjectModel
@using System.Text.Json.Serialization
@using ArcaneLibs.Extensions
@using LibMatrix
@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.RoomTypes
<h3>Fix canonical parent space</h3>
<hr/>
<p>Note: This requires relevant privileges in space children.</p>
<p>You <b>MUST</b> click "check" before executing, otherwise nothing will happen!</p>
<p>Also, worth noting that canonical parent will be set if none or multiple were set previously!</p>
<br/>

<span>Old space ID: </span>
<InputText @bind-Value="@OldSpaceId"></InputText>
<br/>
<span>New space ID: </span>
<InputText @bind-Value="@NewSpaceId"></InputText>
<br/>

<br/>
<LinkButton OnClick="Check">Check</LinkButton>
<LinkButton OnClick="Execute">Execute</LinkButton>
<br/>

<br/>
@foreach (var line in log.Reverse()) {
    <pre>@line</pre>
}

@code {
    private ObservableCollection<string> log { get; set; } = new();

    [Parameter, SupplyParameterFromQuery(Name = "OldSpaceId")]
    public string OldSpaceId { get; set; }

    [Parameter, SupplyParameterFromQuery(Name = "NewSpaceId")]
    public string NewSpaceId { get; set; }

    private AuthenticatedHomeserverGeneric hs { get; set; }
    private Dictionary<GenericRoom, CurrentStatus> _children = new();
    private string[] NewVias = [];

    protected override async Task OnInitializedAsync() {
        log.CollectionChanged += (sender, args) => StateHasChanged();
        hs = await RMUStorage.GetCurrentSessionOrNavigate();
        if (hs is null) return;

        log.Add($"Signed in as {hs.UserId}... Ready for input!");
        StateHasChanged();
        Console.WriteLine("Rerendered!");
        await base.OnInitializedAsync();
    }

    private async Task Check() {
        _children.Clear();
        var newSpace = hs.GetRoom(NewSpaceId).AsSpace;

        await foreach (var room in newSpace.GetChildrenAsync()) {
            try {
                var status = new CurrentStatus();
                status.PowerLevels = await room.GetPowerLevelsAsync();
                status.HasPermission = status.PowerLevels.UserHasStatePermission(hs.UserId, SpaceParentEventContent.EventId);

                status.RoomState = await room.GetFullStateAsListAsync();
                var parentEvents = status.RoomState.Where(x => x.Type == SpaceParentEventContent.EventId);

                status.CurrentCanonicalParents = parentEvents
                    .Where(x => x.RawContent?["canonical"]?.GetValue<bool?>() == true)
                    .Select(x => x.StateKey)
                    .ToList()!;

                // Just for end user usage:
                status.Name = await room.GetNameOrFallbackAsync(maxMemberNames: 4);
                
                log.Add($"{room.RoomId}: {status.ToJson()}");
                _children.Add(room, status);
            }
            catch (MatrixException e) {
                if (e is { ErrorCode: "M_FORBIDDEN" }) {
                    log.Add($"Warning: not in room {room.RoomId}!");
                }
                else {
                    log.Add($"Error checking {room.RoomId}: {e.Message}");
                }
            }
        }

        log.Add("Calculating new Via's...");
        // Not compliant with spec recommendations, but selecting top 10 servers by member count is a fairly safe bet
        var newSpaceMembers = await newSpace.GetMembersByHomeserverAsync();
        NewVias = newSpaceMembers
            .OrderByDescending(x => x.Value.Count)
            .Select(x=>x.Key)
            .Take(10)
            .ToArray();
        log.Add("New vias: " + NewVias.ToJson());
        log.Add("Done! Verify that everything looks okay, and then click Execute!");
    }

    private async Task Execute() {
        foreach (var (room, status) in _children) {
            if (!status.HasPermission) {
                log.Add($"Skipping {room.RoomId} due to lack of permissions!");
                continue;
            }

            log.Add($"Updating parent spaces for {room.RoomId}");
            foreach (var parentId in status.CurrentCanonicalParents) {
                var oldParentEvent = await room.GetStateEventAsync(SpaceParentEventContent.EventId, parentId);
                oldParentEvent.RawContent!["canonical"] = false;
                await room.SendStateEventAsync(SpaceParentEventContent.EventId, parentId, oldParentEvent.RawContent);
            }

            var newParentEvent = new SpaceParentEventContent() {
                Canonical = true,
                Via = NewVias
            };
            
            await room.SendStateEventAsync(SpaceParentEventContent.EventId, NewSpaceId, newParentEvent);
        }

        log.Add("Done!");
    }

    private class CurrentStatus {
        public string Name { get; set; } = "";
        public bool HasPermission { get; set; }
        public List<string> CurrentCanonicalParents { get; set; } = [];

        [JsonIgnore]
        public List<StateEventResponse> RoomState { get; set; } = [];

        [JsonIgnore]
        public RoomPowerLevelEventContent PowerLevels { get; set; } = new();
    }

}