about summary refs log tree commit diff
path: root/MatrixRoomUtils.Web/Pages
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixRoomUtils.Web/Pages')
-rw-r--r--MatrixRoomUtils.Web/Pages/About.razor1
-rw-r--r--MatrixRoomUtils.Web/Pages/DebugTools.razor6
-rw-r--r--MatrixRoomUtils.Web/Pages/DevOptions.razor1
-rw-r--r--MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor22
-rw-r--r--MatrixRoomUtils.Web/Pages/Index.razor55
-rw-r--r--MatrixRoomUtils.Web/Pages/Index.razor.css25
-rw-r--r--MatrixRoomUtils.Web/Pages/InvalidSession.razor7
-rw-r--r--MatrixRoomUtils.Web/Pages/KnownHomeserverList.razor8
-rw-r--r--MatrixRoomUtils.Web/Pages/LoginPage.razor56
-rw-r--r--MatrixRoomUtils.Web/Pages/MediaLocator.razor1
-rw-r--r--MatrixRoomUtils.Web/Pages/ModalTest.razor4
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Create.razor22
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Index.razor10
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor28
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Space.razor2
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor3
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor3
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor8
-rw-r--r--MatrixRoomUtils.Web/Pages/SpaceDebug.razor114
19 files changed, 277 insertions, 99 deletions
diff --git a/MatrixRoomUtils.Web/Pages/About.razor b/MatrixRoomUtils.Web/Pages/About.razor
index 48c7686..17ed04a 100644
--- a/MatrixRoomUtils.Web/Pages/About.razor
+++ b/MatrixRoomUtils.Web/Pages/About.razor
@@ -1,6 +1,5 @@
 @page "/About"
 @using System.Net
-@using System.Net.Sockets
 @inject NavigationManager NavigationManager
 @inject ILocalStorageService LocalStorage
 
diff --git a/MatrixRoomUtils.Web/Pages/DebugTools.razor b/MatrixRoomUtils.Web/Pages/DebugTools.razor
index afb1da2..5d47277 100644
--- a/MatrixRoomUtils.Web/Pages/DebugTools.razor
+++ b/MatrixRoomUtils.Web/Pages/DebugTools.razor
@@ -1,7 +1,9 @@
 @page "/Debug"
 @using System.Reflection
+@using ArcaneLibs.Extensions
+@using LibMatrix
 @using LibMatrix.Extensions
-@using LibMatrix.Interfaces
+@using LibMatrix.Homeservers
 @inject ILocalStorageService LocalStorage
 @inject NavigationManager NavigationManager
 <h3>Debug Tools</h3>
@@ -50,7 +52,7 @@ else {
     string get_request_result { get; set; } = "";
 
     private async Task SendGetRequest() {
-        var field = typeof(IHomeServer).GetRuntimeFields().First(x => x.ToString().Contains("<_httpClient>k__BackingField"));
+        var field = typeof(RemoteHomeServer).GetRuntimeFields().First(x => x.ToString().Contains("<_httpClient>k__BackingField"));
         var hs = await MRUStorage.GetCurrentSessionOrNavigate();
         if (hs == null) return;
         var httpClient = field.GetValue(hs) as MatrixHttpClient;
diff --git a/MatrixRoomUtils.Web/Pages/DevOptions.razor b/MatrixRoomUtils.Web/Pages/DevOptions.razor
index bf499a3..8511a26 100644
--- a/MatrixRoomUtils.Web/Pages/DevOptions.razor
+++ b/MatrixRoomUtils.Web/Pages/DevOptions.razor
@@ -1,5 +1,6 @@
 @page "/DevOptions"
 @using LibMatrix.Extensions
+@using ArcaneLibs.Extensions
 @inject NavigationManager NavigationManager
 @inject ILocalStorageService LocalStorage
 
diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
index 679f324..a4f9d97 100644
--- a/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
+++ b/MatrixRoomUtils.Web/Pages/HSAdmin/RoomQuery.razor
@@ -1,8 +1,10 @@
 @page "/HSAdmin/RoomQuery"
-@using MatrixRoomUtils.Web.Shared.SimpleComponents
 @using LibMatrix.Responses.Admin
 @using LibMatrix.Filters
 @using LibMatrix.Extensions
+@using LibMatrix
+@using LibMatrix.Homeservers
+@using ArcaneLibs.Extensions
 
 <h3>Homeserver Administration - Room Query</h3>
 
@@ -168,15 +170,17 @@
     private async Task Search() {
         Results.Clear();
         var hs = await MRUStorage.GetCurrentSessionOrNavigate();
-        if (hs is null) return;
-        var searchRooms = hs.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator();
-        while (await searchRooms.MoveNextAsync()) {
-            var room = searchRooms.Current;
-            Console.WriteLine("Hit: " + room.ToJson(false));
-            Results.Add(room);
-            if (Results.Count % 10 == 0)
-                StateHasChanged();
+        if (hs is AuthenticatedHomeserverSynapse synapse) {
+            var searchRooms = synapse.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator();
+            while (await searchRooms.MoveNextAsync()) {
+                var room = searchRooms.Current;
+                Console.WriteLine("Hit: " + room.ToJson(false));
+                Results.Add(room);
+                if (Results.Count % 10 == 0)
+                    StateHasChanged();
+            }
         }
+
     }
 
     private readonly Dictionary<string, string> validOrderBy = new() {
diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor
index 1004ee3..e02c733 100644
--- a/MatrixRoomUtils.Web/Pages/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Index.razor
@@ -1,8 +1,9 @@
 @page "/"
-@using MatrixRoomUtils.Web.Shared.SimpleComponents
 @using LibMatrix.Responses
 @using LibMatrix
 @using LibMatrix.Helpers
+@using LibMatrix.Homeservers
+@using ArcaneLibs.Extensions
 
 <PageTitle>Index</PageTitle>
 
@@ -13,25 +14,38 @@ Small collection of tools to do not-so-everyday things.
 <h5>Signed in accounts - <a href="/Login">Add new account</a></h5>
 <hr/>
 <form>
-    @foreach (var (auth, user) in _users.OrderByDescending(x=>x.Value.RoomCount)) {
-        var _auth = auth;
-        <div style="margin-bottom: 1em;">
-            <img style="border-radius: 50%; height: 3em; width: 3em;" src="@user.AvatarUrl"/>
-            <p style="margin-left: 1em; margin-top: -0.5em; display: inline-block;">
-                <input type="radio" name="csa" checked="@(_currentSession.AccessToken == _auth.AccessToken)" @onclick="@(()=>SwitchSession(_auth))" style="text-decoration-line: unset;"/>
-                <b>@user.DisplayName</b> on <b>@_auth.Homeserver</b>
-                <a role="button" @onclick="@(() => RemoveUser(_auth))">Remove</a>
+    <table>
+        @foreach (var (auth, user) in _users.OrderByDescending(x => x.Value.RoomCount)) {
+            var _auth = auth;
+            <tr class="user-entry">
+                <td>
+                    <img class="avatar" src="@user.AvatarUrl"/>
+                </td>
+                <td class="user-info">
+                    @* <div class="user-info"> *@
+                    <p>
+                        <input type="radio" name="csa" checked="@(_currentSession.AccessToken == _auth.AccessToken)" @onclick="@(() => SwitchSession(_auth))" style="text-decoration-line: unset;"/>
+                        <b>@user.DisplayName</b> on <b>@_auth.Homeserver</b>
+                    </p>
+                    <p>Member of @user.RoomCount rooms</p>
 
-            </p>
-            <p style="margin-top: -1.5em; margin-left: 4em;">Member of @user.RoomCount rooms</p>
-
-        </div>
-    }
+                    <p>Not proxied</p>
+                </td>
+                <td>
+                    <p>
+                        <LinkButton href="">Manage</LinkButton>
+                        <LinkButton OnClick="@(() => RemoveUser(_auth))">Remove</LinkButton>
+                    </p>
+                </td>
+                @* </div> *@
+            </tr>
+        }
+    </table>
 </form>
 
 @code
 {
-    private Dictionary<LoginResponse, UserInfo> _users = new();
+    private Dictionary<UserAuth, UserInfo> _users = new();
 
     protected override async Task OnInitializedAsync() {
         _currentSession = await MRUStorage.GetCurrentToken();
@@ -39,13 +53,13 @@ Small collection of tools to do not-so-everyday things.
         var tokens = await MRUStorage.GetAllTokens();
         var profileTasks = tokens.Select(async token => {
             UserInfo userInfo = new();
-            AuthenticatedHomeServer hs;
+            AuthenticatedHomeserverGeneric hs;
             try {
                 hs = await HomeserverProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken);
             }
             catch (MatrixException e) {
                 if (e.ErrorCode == "M_UNKNOWN_TOKEN") {
-                    NavigationManager.NavigateTo("/InvalidSession?ctx="+token.AccessToken);
+                    NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken);
                     return;
                 }
                 throw;
@@ -53,6 +67,7 @@ Small collection of tools to do not-so-everyday things.
             var roomCountTask = hs.GetJoinedRooms();
             var profile = await hs.GetProfile(hs.WhoAmI.UserId);
             userInfo.DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId;
+            Console.WriteLine(profile.ToJson());
             userInfo.AvatarUrl = MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain,
                 profile.AvatarUrl
                 ?? "https://api.dicebear.com/6.x/identicon/svg?seed=" + hs.WhoAmI.UserId
@@ -71,7 +86,7 @@ Small collection of tools to do not-so-everyday things.
         internal int RoomCount { get; set; }
     }
 
-    private async Task RemoveUser(LoginResponse auth) {
+    private async Task RemoveUser(UserAuth auth) {
         await MRUStorage.RemoveToken(auth);
         if ((await MRUStorage.GetCurrentToken()).AccessToken == auth.AccessToken)
             MRUStorage.SetCurrentToken((await MRUStorage.GetAllTokens()).FirstOrDefault());
@@ -80,8 +95,8 @@ Small collection of tools to do not-so-everyday things.
 
     private LoginResponse _currentSession;
 
-    private async Task SwitchSession(LoginResponse auth) {
-        Console.WriteLine($"Switching to {auth.Homeserver} {auth.AccessToken} {auth.UserId}");
+    private async Task SwitchSession(UserAuth auth) {
+        Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}");
         await MRUStorage.SetCurrentToken(auth);
         await OnInitializedAsync();
     }
diff --git a/MatrixRoomUtils.Web/Pages/Index.razor.css b/MatrixRoomUtils.Web/Pages/Index.razor.css
new file mode 100644
index 0000000..c6b7bd7
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/Index.razor.css
@@ -0,0 +1,25 @@
+.user-entry {
+    margin-bottom: 1em;
+}
+
+.avatar {
+    width: 4em;
+    height: 4em;
+    border-radius: 50%;
+    margin-right: 0.5em;
+    vertical-align: middle;
+}
+
+.user-entry > td {
+    margin-right: 0.5em;
+    vertical-align: middle;
+}
+
+.user-info {
+    margin-bottom: 0.5em;
+    display: inline-block;
+    vertical-align: middle;
+}
+.user-info > p {
+    margin: 0;
+}
diff --git a/MatrixRoomUtils.Web/Pages/InvalidSession.razor b/MatrixRoomUtils.Web/Pages/InvalidSession.razor
index f555be5..310abb1 100644
--- a/MatrixRoomUtils.Web/Pages/InvalidSession.razor
+++ b/MatrixRoomUtils.Web/Pages/InvalidSession.razor
@@ -1,5 +1,4 @@
 @page "/InvalidSession"
-@using MatrixRoomUtils.Web.Shared.SimpleComponents
 @using LibMatrix.Responses
 @using LibMatrix
 
@@ -33,7 +32,7 @@ else {
     [SupplyParameterFromQuery(Name = "ctx")]
     public string Context { get; set; }
 
-    private LoginResponse? _login { get; set; }
+    private UserAuth? _login { get; set; }
 
     private bool _showRefreshDialog { get; set; }
 
@@ -70,7 +69,7 @@ else {
         await Task.CompletedTask;
     }
 
-    private async Task SwitchSession(LoginResponse auth) {
+    private async Task SwitchSession(UserAuth auth) {
         Console.WriteLine($"Switching to {auth.Homeserver} {auth.AccessToken} {auth.UserId}");
         await MRUStorage.SetCurrentToken(auth);
         await OnInitializedAsync();
@@ -79,7 +78,7 @@ else {
     private async Task TryLogin() {
         if(_login is null) throw new NullReferenceException("Login is null!");
         try {
-            var result = await HomeserverProvider.Login(_login.Homeserver, _login.UserId, _password);
+            var result = new UserAuth(await HomeserverProvider.Login(_login.Homeserver, _login.UserId, _password));
             if (result is null) {
                 Console.WriteLine($"Failed to login to {_login.Homeserver} as {_login.UserId}!");
                 return;
diff --git a/MatrixRoomUtils.Web/Pages/KnownHomeserverList.razor b/MatrixRoomUtils.Web/Pages/KnownHomeserverList.razor
index 22a004d..4cd2032 100644
--- a/MatrixRoomUtils.Web/Pages/KnownHomeserverList.razor
+++ b/MatrixRoomUtils.Web/Pages/KnownHomeserverList.razor
@@ -1,10 +1,10 @@
 @page "/KnownHomeserverList"
-@using System.Text.Json
 @using System.Diagnostics
+@using ArcaneLibs.Extensions
 @using LibMatrix
 @using LibMatrix.Extensions
+@using LibMatrix.Homeservers
 @using LibMatrix.RoomTypes
-@using LibMatrix.StateEventTypes
 <h3>Known Homeserver List</h3>
 <hr/>
 
@@ -36,7 +36,7 @@ else {
     List<HomeServerInfo> HomeServers = new();
     bool IsFinished { get; set; }
     HomeServerInfoQueryProgress QueryProgress { get; set; } = new();
-    AuthenticatedHomeServer hs { get; set; }
+    AuthenticatedHomeserverGeneric hs { get; set; }
     protected override async Task OnInitializedAsync() {
         hs = await MRUStorage.GetCurrentSessionOrNavigate();
         if (hs is null) return;
@@ -91,7 +91,7 @@ else {
 
 
 
-    //         states.RemoveAll(x => x.Type != "m.room.member" || (x.TypedContent as RoomMemberEventData).Membership != "join");
+    //         states.RemoveAll(x => x.Type != "m.room.member" || (x.TypedContent as RoomMemberEventContent).Membership != "join");
     //         Console.WriteLine($"Room {room.RoomId} has {states.Count} members");
     //         if (states.Count > memberLimit) {
     //             Console.WriteLine("Skipping!");
diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor
index 9730cbe..a6ce469 100644
--- a/MatrixRoomUtils.Web/Pages/LoginPage.razor
+++ b/MatrixRoomUtils.Web/Pages/LoginPage.razor
@@ -1,7 +1,6 @@
 @page "/Login"
 @using System.Text.Json
 @using LibMatrix.Responses
-@using MatrixRoomUtils.Web.Shared.SimpleComponents
 @inject ILocalStorageService LocalStorage
 @inject IJSRuntime JsRuntime
 <h3>Login</h3>
@@ -12,6 +11,8 @@
     --><FancyTextBox @bind-Value="@newRecordInput.username"></FancyTextBox><!--
     --><span>:</span><!--
     --><FancyTextBox @bind-Value="@newRecordInput.homeserver"></FancyTextBox>
+    via
+    <FancyTextBox @bind-Value="@newRecordInput.password" IsPassword="true"></FancyTextBox>
 </span>
 <span style="display: block;">
     <label>Password:</label>
@@ -29,13 +30,27 @@
     <thead>
         <td>Username</td>
         <td>Homeserver</td>
+        <td>Password</td>
+        <td>Proxy</td>
     </thead>
-    @foreach (var (homeserver, username, password) in records) {
-        var record = (homeserver, username, password);
-        <tr style="background-color: @(LoggedInSessions.Any(x => x.UserId == $"@{username}:{homeserver}") ? "green" : "unset")">
-            <td style="border-width: 1px;">@username</td>
-            <td style="border-width: 1px;">@homeserver</td>
-            <td><a role="button" @onclick="() => records.Remove(record)">Remove</a></td>
+    @foreach (var record in records) {
+        var r = record;
+        <tr style="background-color: @(LoggedInSessions.Any(x => x.UserId == $"@{r.username}:{r.homeserver}" && x.Proxy == r.proxy) ? "green" : "unset")">
+            <td style="border-width: 1px;">
+                <FancyTextBox @bind-Value="@r.homeserver"></FancyTextBox>
+            </td>
+            <td style="border-width: 1px;">
+                <FancyTextBox @bind-Value="@r.username"></FancyTextBox>
+            </td>
+            <td style="border-width: 1px;">
+                <FancyTextBox @bind-Value="@r.password" IsPassword="true"></FancyTextBox>
+            </td>
+            <td style="border-width: 1px;">
+                <FancyTextBox @bind-Value="@r.proxy"></FancyTextBox>
+            </td>
+            <td>
+                <a role="button" @onclick="() => records.Remove(r)">Remove</a>
+            </td>
         </tr>
     }
 </table>
@@ -45,17 +60,19 @@
 <LogView></LogView>
 
 @code {
-    readonly List<(string homeserver, string username, string password)> records = new();
-    (string homeserver, string username, string password) newRecordInput = ("", "", "");
+    readonly List<(string homeserver, string username, string password, string? proxy)> records = new();
+    (string homeserver, string username, string password, string? proxy) newRecordInput = ("", "", "", null);
 
-    List<LoginResponse> LoggedInSessions { get; set; } = new();
+    List<UserAuth>? LoggedInSessions { get; set; } = new();
 
     async Task Login() {
         var loginTasks = records.Select(async record => {
-            var (homeserver, username, password) = record;
-            if (LoggedInSessions.Any(x => x.UserId == $"@{username}:{homeserver}")) return;
+            var (homeserver, username, password, proxy) = record;
+            if (LoggedInSessions.Any(x => x.UserId == $"@{username}:{homeserver}" && x.Proxy == proxy)) return;
             try {
-                var result = await HomeserverProvider.Login(homeserver, username, password);
+                var result = new UserAuth(await HomeserverProvider.Login(homeserver, username, password, proxy)) {
+                    Proxy = proxy
+                };
                 if (result == null) {
                     Console.WriteLine($"Failed to login to {homeserver} as {username}!");
                     return;
@@ -81,20 +98,21 @@
         }));
         await using var rs = obj.File.OpenReadStream();
         using var sr = new StreamReader(rs);
-        var TsvData = await sr.ReadToEndAsync();
+        var tsvData = await sr.ReadToEndAsync();
         records.Clear();
-        foreach (var line in TsvData.Split('\n')) {
-            var parts = line.Split('\t');
-            if (parts.Length != 3)
+        foreach (var line in tsvData.Split('\n')) {
+            string?[] parts = line.Split('\t');
+            if (parts.Length < 3)
                 continue;
-            records.Add((parts[0], parts[1], parts[2]));
+            string? via = parts.Length > 3 ? parts[3] : null;
+            records.Add((parts[0], parts[1], parts[2], via));
         }
     }
 
     private async Task AddRecord() {
         LoggedInSessions = await MRUStorage.GetAllTokens();
         records.Add(newRecordInput);
-        newRecordInput = ("", "", "");
+        newRecordInput = ("", "", "", null);
     }
 
 }
diff --git a/MatrixRoomUtils.Web/Pages/MediaLocator.razor b/MatrixRoomUtils.Web/Pages/MediaLocator.razor
index af6e67a..42c7b8e 100644
--- a/MatrixRoomUtils.Web/Pages/MediaLocator.razor
+++ b/MatrixRoomUtils.Web/Pages/MediaLocator.razor
@@ -1,5 +1,6 @@
 @page "/MediaLocator"
 @using LibMatrix
+@using LibMatrix.Homeservers
 @inject HttpClient Http
 <h3>Media locator</h3>
 <hr/>
diff --git a/MatrixRoomUtils.Web/Pages/ModalTest.razor b/MatrixRoomUtils.Web/Pages/ModalTest.razor
index 2b1c9bc..1d14005 100644
--- a/MatrixRoomUtils.Web/Pages/ModalTest.razor
+++ b/MatrixRoomUtils.Web/Pages/ModalTest.razor
@@ -10,7 +10,7 @@
      <ModalWindow X="@Random.Shared.Next(1400)" Y="@Random.Shared.Next(1000)" Title="@("Window " + i1)" OnCloseClicked="() => OnCloseClicked(i1)">
           @for (var j = 0; j < i1; j++) {
               <h1>@j</h1>
-          } 
+          }
      </ModalWindow>
 }
 
@@ -70,7 +70,7 @@
             }
             if(_windowInfos.Count > 750) multiplier = 2;
             if(_windowInfos.Count > 1500) multiplier = 3;
-            
+
         }
 
         await base.OnInitializedAsync();
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
index 3b7d000..c6fd5b6 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
@@ -1,15 +1,15 @@
 @page "/Rooms/Create"
 @using System.Text.Json
 @using System.Reflection
+@using ArcaneLibs.Extensions
 @using LibMatrix
 @using LibMatrix.Extensions
 @using LibMatrix.Helpers
+@using LibMatrix.Homeservers
 @using LibMatrix.Responses
 @using LibMatrix.StateEventTypes.Spec
-@using LibMatrix.StateEventTypes
 @using MatrixRoomUtils.Web.Classes.RoomCreationTemplates
 @* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@
-@using MatrixRoomUtils.Web.Shared.SimpleComponents
 
 <h3>Room Manager - Create Room</h3>
 
@@ -135,7 +135,7 @@
                 }
                 else {
                     <details>
-                        <summary>@((creationEvent["m.room.server_acls"].TypedContent as ServerACLEventData).Allow.Count) allow rules</summary>
+                        <summary>@((creationEvent["m.room.server_acls"].TypedContent as ServerACLEventContent).Allow.Count) allow rules</summary>
                         @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@
                     </details>
                 }
@@ -145,7 +145,7 @@
                 }
                 else {
                     <details>
-                        <summary>@((creationEvent["m.room.server_acls"].TypedContent as ServerACLEventData).Deny.Count) deny rules</summary>
+                        <summary>@((creationEvent["m.room.server_acls"].TypedContent as ServerACLEventContent).Deny.Count) deny rules</summary>
                         @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@
                     </details>
                 }
@@ -251,14 +251,14 @@
     private CreateRoomRequest? creationEvent { get; set; }
 
     private Dictionary<string, CreateRoomRequest>? Presets { get; set; } = new();
-    private AuthenticatedHomeServer? HomeServer { get; set; }
+    private AuthenticatedHomeserverGeneric? HomeServer { get; set; }
 
     private MatrixException? _matrixException { get; set; }
 
-    private HistoryVisibilityEventData? historyVisibility => creationEvent?["m.room.history_visibility"].TypedContent as HistoryVisibilityEventData;
-    private GuestAccessEventData? guestAccessEvent => creationEvent?["m.room.guest_access"].TypedContent as GuestAccessEventData;
-    private ServerACLEventData? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as ServerACLEventData;
-    private RoomAvatarEventData? roomAvatarEvent => creationEvent?["m.room.avatar"].TypedContent as RoomAvatarEventData;
+    private HistoryVisibilityEventContent? historyVisibility => creationEvent?["m.room.history_visibility"].TypedContent as HistoryVisibilityEventContent;
+    private GuestAccessEventContent? guestAccessEvent => creationEvent?["m.room.guest_access"].TypedContent as GuestAccessEventContent;
+    private ServerACLEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as ServerACLEventContent;
+    private RoomAvatarEventContent? roomAvatarEvent => creationEvent?["m.room.avatar"].TypedContent as RoomAvatarEventContent;
 
     protected override async Task OnInitializedAsync() {
         HomeServer = await MRUStorage.GetCurrentSessionOrNavigate();
@@ -284,7 +284,7 @@
     private async Task RoomIconFilePicked(InputFileChangeEventArgs obj) {
         var res = await HomeServer.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType);
         Console.WriteLine(res);
-        (creationEvent["m.room.avatar"].TypedContent as RoomAvatarEventData).Url = res;
+        (creationEvent["m.room.avatar"].TypedContent as RoomAvatarEventContent).Url = res;
         StateHasChanged();
     }
 
@@ -305,7 +305,7 @@
             creationEvent.InitialState.Add(new StateEvent {
                 Type = "m.room.member",
                 StateKey = mxid,
-                TypedContent = new RoomMemberEventData {
+                TypedContent = new RoomMemberEventContent {
                     Membership = "invite",
                     Reason = "Automatically invited at room creation time."
                 }
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
index ad3a714..c2daba7 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -1,10 +1,10 @@
 @page "/Rooms"
-@using LibMatrix.StateEventTypes
 @using LibMatrix.StateEventTypes.Spec
 @using LibMatrix.Filters
 @using LibMatrix.Helpers
 @using LibMatrix.Responses
 <h3>Room list</h3>
+
 <p>@Status</p>
 @if (RenderContents) {
     <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile"></RoomList>
@@ -16,7 +16,7 @@
     public List<RoomInfo> KnownRooms { get; set; } = new();
 
     private List<RoomInfo> Rooms { get; set; } = new();
-    private ProfileResponseEventData GlobalProfile { get; set; }
+    private ProfileResponseEventContent GlobalProfile { get; set; }
 
     private SyncFilter filter = new() {
         AccountData = new SyncFilter.EventFilter {
@@ -93,7 +93,7 @@
             if (!roomInfo.StateEvents.Any(x => x.Type == "m.room.name")) {
                 roomInfo.StateEvents.Add(new StateEventResponse {
                     Type = "m.room.name",
-                    TypedContent = new RoomNameEventData {
+                    TypedContent = new RoomNameEventContent {
                         Name = roomInfo.Room.RoomId
                     }
                 });
@@ -101,7 +101,7 @@
             if (!roomInfo.StateEvents.Any(x => x.Type == "m.room.avatar")) {
                 roomInfo.StateEvents.Add(new StateEventResponse {
                     Type = "m.room.avatar",
-                    TypedContent = new RoomAvatarEventData {
+                    TypedContent = new RoomAvatarEventContent {
 
                     }
                 });
@@ -121,7 +121,7 @@
                 roomInfo.StateEvents.Add(new StateEventResponse {
                     Type = "m.room.member",
                     StateKey = hs.WhoAmI.UserId,
-                    TypedContent = await roomInfo.Room.GetStateAsync<RoomMemberEventData>("m.room.member", hs.WhoAmI.UserId) ?? new RoomMemberEventData {
+                    TypedContent = await roomInfo.Room.GetStateAsync<RoomMemberEventContent>("m.room.member", hs.WhoAmI.UserId) ?? new RoomMemberEventContent {
                         Membership = "unknown"
                     }
                 });
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
index 2b31389..d2b8360 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
@@ -1,11 +1,11 @@
 @page "/Rooms/{RoomId}/Policies"
-@using LibMatrix.StateEventTypes
-@using System.Text.Json
 @using LibMatrix
 @using LibMatrix.Extensions
 @using LibMatrix.Helpers
+@using LibMatrix.Homeservers
 @using LibMatrix.Responses
 @using LibMatrix.StateEventTypes.Spec
+@using ArcaneLibs.Extensions
 <h3>Policy list editor - Editing @RoomId</h3>
 <hr/>
 
@@ -33,8 +33,8 @@ else {
         </tr>
         </thead>
         <tbody>
-        @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
-            var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+        @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
+            var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
             <tr>
                 <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
                 <td>@policyData.Reason</td>
@@ -59,8 +59,8 @@ else {
             </tr>
             </thead>
             <tbody>
-            @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleStateEventData).Entity == null)) {
-                var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+            @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) {
+                var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
                 <tr>
                     <td>@policyEvent.StateKey</td>
                     <td>@policyEvent.RawContent.ToJson(false, true)</td>
@@ -86,8 +86,8 @@ else {
         </tr>
         </thead>
         <tbody>
-        @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
-            var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+        @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
+            var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
             <tr>
                 <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
                 <td>@policyData.Reason</td>
@@ -111,7 +111,7 @@ else {
             </tr>
             </thead>
             <tbody>
-            @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleStateEventData).Entity == null)) {
+            @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) {
                 <tr>
                     <td>@policyEvent.StateKey</td>
                     <td>@policyEvent.RawContent!.ToJson(false, true)</td>
@@ -140,8 +140,8 @@ else {
         </tr>
         </thead>
         <tbody>
-        @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
-            var policyData = policyEvent.TypedContent as PolicyRuleStateEventData;
+        @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
+            var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
             <tr>
                 @if (_enableAvatars) {
                     <td scope="col">
@@ -170,7 +170,7 @@ else {
             </tr>
             </thead>
             <tbody>
-            @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleStateEventData).Entity == null)) {
+            @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) {
                 <tr>
                     <td>@policyEvent.StateKey</td>
                     <td>@policyEvent.RawContent.ToJson(false, true)</td>
@@ -245,8 +245,8 @@ else {
     }
 
     private async Task GetAllAvatars() {
-        foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleStateEventData).Entity is not null)) {
-            await GetAvatar((policyEvent.TypedContent as PolicyRuleStateEventData).Entity);
+        foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
+            await GetAvatar((policyEvent.TypedContent as PolicyRuleEventContent).Entity);
         }
         StateHasChanged();
     }
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Space.razor b/MatrixRoomUtils.Web/Pages/Rooms/Space.razor
index c37b8ab..ef0ea5a 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Space.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Space.razor
@@ -1,8 +1,8 @@
 @page "/Rooms/{RoomId}/Space"
-@using System.Text.Json
 @using LibMatrix.Extensions
 @using LibMatrix.Responses
 @using LibMatrix.RoomTypes
+@using ArcaneLibs.Extensions
 <h3>Room manager - Viewing Space</h3>
 
 <button onclick="@JoinAllRooms">Join all rooms</button>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor
index ef7cd51..fefcabc 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor
@@ -1,8 +1,7 @@
 @page "/Rooms/{RoomId}/State/Edit"
-@using System.Net.Http.Headers
-@using System.Text.Json
 @using LibMatrix.Extensions
 @using LibMatrix.Responses
+@using ArcaneLibs.Extensions
 @inject ILocalStorageService LocalStorage
 @inject NavigationManager NavigationManager
 <h3>Room state editor - Editing @RoomId</h3>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor
index 5a48b32..1c3f28b 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor
@@ -1,8 +1,7 @@
 @page "/Rooms/{RoomId}/State/View"
-@using System.Net.Http.Headers
-@using System.Text.Json
 @using LibMatrix.Extensions
 @using LibMatrix.Responses
+@using ArcaneLibs.Extensions
 @inject ILocalStorageService LocalStorage
 @inject NavigationManager NavigationManager
 <h3>Room state viewer - Viewing @RoomId</h3>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor
index 4a5298b..2c95c99 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor
@@ -1,6 +1,7 @@
 @page "/Rooms/{RoomId}/Timeline"
 @using MatrixRoomUtils.Web.Shared.TimelineComponents
 @using LibMatrix
+@using LibMatrix.Homeservers
 @using LibMatrix.Responses
 @using LibMatrix.StateEventTypes.Spec
 <h3>RoomManagerTimeline</h3>
@@ -23,7 +24,7 @@
     private List<MessagesResponse> Messages { get; } = new();
     private List<StateEventResponse> Events { get; } = new();
 
-    private AuthenticatedHomeServer? HomeServer { get; set; }
+    private AuthenticatedHomeserverGeneric? HomeServer { get; set; }
 
     protected override async Task OnInitializedAsync() {
         Console.WriteLine("RoomId: " + RoomId);
@@ -46,8 +47,9 @@
     private StateEventResponse GetProfileEventBefore(StateEventResponse Event) => Events.TakeWhile(x => x != Event).Last(e => e.Type == "m.room.member" && e.StateKey == Event.Sender);
 
     private Type ComponentType(StateEvent Event) => Event.TypedContent switch {
-        RoomMessageEventData => typeof(TimelineMessageItem),
-        RoomMemberEventData => typeof(TimelineMemberItem),
+        RoomMessageEventContent => typeof(TimelineMessageItem),
+        RoomMemberEventContent => typeof(TimelineMemberItem),
+        RoomCreateEventContent => typeof(TimelineRoomCreateItem),
         _ => typeof(TimelineUnknownItem)
         };
 
diff --git a/MatrixRoomUtils.Web/Pages/SpaceDebug.razor b/MatrixRoomUtils.Web/Pages/SpaceDebug.razor
new file mode 100644
index 0000000..c4c4ce8
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/SpaceDebug.razor
@@ -0,0 +1,114 @@
+@page "/SpaceDebug"
+@using LibMatrix.StateEventTypes.Spec
+@using LibMatrix.RoomTypes
+@using LibMatrix.Filters
+<h3>SpaceDebug</h3>
+<hr/>
+
+<p>@Status</p>
+
+<b>Has parent:</b>
+<br/>
+
+@foreach (var (roomId, parents) in SpaceParents) {
+    <p>@roomId's parents</p>
+    <ul>
+        @foreach (var parent in parents) {
+            <li>@parent</li>
+        }
+    </ul>
+}
+
+<b>Space children:</b>
+
+@foreach (var (roomId, children) in SpaceChildren) {
+    <p>@roomId's children</p>
+    <ul>
+        @foreach (var child in children) {
+            <li>@child</li>
+        }
+    </ul>
+}
+
+@code {
+    private string _status = "Loading...";
+
+    public string Status {
+        get => _status;
+        set {
+            _status = value;
+            StateHasChanged();
+        }
+    }
+
+    public Dictionary<string, List<string>> SpaceChildren { get; set; } = new();
+    public Dictionary<string, List<string>> SpaceParents { get; set; } = new();
+
+    protected override async Task OnInitializedAsync() {
+        Status = "Getting homeserver...";
+        var hs = await MRUStorage.GetCurrentSessionOrNavigate();
+        if (hs is null) return;
+
+        Status = "Syncing...";
+        string nextBatch = null;
+        while (nextBatch != "end") {
+            var sync = await hs.SyncHelper.Sync(since: nextBatch, filter: new SyncFilter() {
+                Presence = new(0),
+                Room = new() {
+                    AccountData = new(limit: 0),
+                    Ephemeral = new(limit: 0),
+                    State = new(limit: 1000, types: new() { "m.space.child", "m.space.parent" }),
+                    Timeline = new(limit: 0)
+                },
+                AccountData = new(limit: 0)
+            });
+
+            if (sync is null) {
+                Status = "Sync failed";
+                continue;
+            }
+
+            if (sync.Rooms is null) {
+                Status = "No rooms in sync...";
+                nextBatch = "end";
+                continue;
+            }
+
+            if (sync.Rooms.Join is null) {
+                Status = "No joined rooms in sync...";
+                nextBatch = "end";
+                continue;
+            }
+
+            if (sync.Rooms.Join.Count == 0) {
+                Status = "Joined rooms list was empty...";
+                nextBatch = "end";
+                continue;
+            }
+
+            nextBatch = sync.NextBatch;
+            foreach (var (roomId, data) in sync.Rooms!.Join!) {
+                data.State?.Events?.ForEach(e => {
+                    if (e.Type == "m.space.child") {
+                        if (!SpaceChildren.ContainsKey(roomId)) SpaceChildren[roomId] = new();
+                        if (e.RawContent is null) e.StateKey += " (null)";
+                        else if (e.RawContent.Count == 0) e.StateKey += " (empty)";
+                        SpaceChildren[roomId].Add(e.StateKey);
+                    }
+                    if (e.Type == "m.space.parent") {
+                        if (!SpaceParents.ContainsKey(roomId)) SpaceParents[roomId] = new();
+                        if (e.RawContent is null) e.StateKey += " (null)";
+                        else if (e.RawContent.Count == 0) e.StateKey += " (empty)";
+                        SpaceParents[roomId].Add(e.StateKey);
+                    }
+                });
+            }
+            Status = $"Synced {sync.Rooms.Join.Count} rooms, found {SpaceChildren.Count} spaces, {SpaceParents.Count} parents";
+        }
+        Status = $"Synced: found {SpaceChildren.Count}->{SpaceChildren.Sum(x => x.Value.Count)} spaces, {SpaceParents.Count}->{SpaceParents.Sum(x => x.Value.Count)} parents!";
+
+        await base.OnInitializedAsync();
+    }
+
+
+}