diff options
Diffstat (limited to 'MatrixRoomUtils.Web/Pages')
-rw-r--r-- | MatrixRoomUtils.Web/Pages/DataExportPage.razor | 83 | ||||
-rw-r--r-- | MatrixRoomUtils.Web/Pages/Index.razor | 33 | ||||
-rw-r--r-- | MatrixRoomUtils.Web/Pages/LoginPage.razor | 46 | ||||
-rw-r--r-- | MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor | 232 | ||||
-rw-r--r-- | MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor | 84 | ||||
-rw-r--r-- | MatrixRoomUtils.Web/Pages/UserImportPage.razor | 74 |
6 files changed, 552 insertions, 0 deletions
diff --git a/MatrixRoomUtils.Web/Pages/DataExportPage.razor b/MatrixRoomUtils.Web/Pages/DataExportPage.razor new file mode 100644 index 0000000..a7c6f6b --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/DataExportPage.razor @@ -0,0 +1,83 @@ +@page "/export" +@using MatrixRoomUtils.Web.Classes +@using MatrixRoomUtils.Web.Shared.IndexComponents +@using Blazored.LocalStorage +@using MatrixRoomUtils.Authentication +@using System.Text.Json +@using Microsoft.AspNetCore.Components.Rendering +@inject NavigationManager NavigationManager +@inject ILocalStorageService LocalStorage + +<PageTitle>Export</PageTitle> + +<h1>Data export</h1> + +<br/><br/> +<h5>Signed in accounts - <a href="/Login">Add new account</a> or <a href="/ImportUsers">Import from TSV</a></h5> +<hr/> +@if (_isLoaded) +{ +@foreach (var (token, user) in RuntimeStorage.UsersCache) +{ + <IndexUserItem User="@user" Token="@token"/> + <pre> +@user.LoginResponse.UserId[1..].Split(":")[0]\auth\access_token=@token +@user.LoginResponse.UserId[1..].Split(":")[0]\auth\device_id=@user.LoginResponse.DeviceId +@user.LoginResponse.UserId[1..].Split(":")[0]\auth\home_server=@(RuntimeStorage.HomeserverResolutionCache.ContainsKey(user.LoginResponse.HomeServer) ? RuntimeStorage.HomeserverResolutionCache[user.LoginResponse.HomeServer].Result : "loading...") +@user.LoginResponse.UserId[1..].Split(":")[0]\auth\user_id=@@@user.LoginResponse.UserId +@user.LoginResponse.UserId[1..].Split(":")[0]\user\automatically_share_keys_with_trusted_users=true +@user.LoginResponse.UserId[1..].Split(":")[0]\user\muted_tags=global +@user.LoginResponse.UserId[1..].Split(":")[0]\user\online_key_backup=true +@user.LoginResponse.UserId[1..].Split(":")[0]\user\only_share_keys_with_verified_users=false + </pre> +} +} +else +{ + <p>Loading...</p> + <p>@resolvedHomeservers/@totalHomeservers homeservers resolved...</p> +} + +@code { + private bool _isLoaded; + private int resolvedHomeservers; + private int totalHomeservers; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count); + if (!RuntimeStorage.WasLoaded) + { + Console.WriteLine("[INDEX] !!! LOCALSTORAGE WAS NOT LOADED !!!"); + await RuntimeStorage.LoadFromLocalStorage(LocalStorage); + + Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count); + + var homeservers = RuntimeStorage.UsersCache.Values.Select(x => x.LoginResponse.HomeServer).Distinct(); + totalHomeservers = homeservers.Count(); + StateHasChanged(); + foreach (var hs in homeservers) + { + if (RuntimeStorage.HomeserverResolutionCache.ContainsKey(hs)) continue; + var resolvedHomeserver = await MatrixAccount.ResolveHomeserverFromWellKnown(hs); + + if (RuntimeStorage.HomeserverResolutionCache.ContainsKey(hs)) + RuntimeStorage.HomeserverResolutionCache.Remove(hs); + RuntimeStorage.HomeserverResolutionCache.Add(hs, new(){Result = resolvedHomeserver, ResolutionTime = DateTime.Now}); + await RuntimeStorage.SaveToLocalStorage(LocalStorage); + + Console.WriteLine("Saved to local storage:"); + Console.WriteLine(JsonSerializer.Serialize(RuntimeStorage.HomeserverResolutionCache, new JsonSerializerOptions() + { + WriteIndented = true + })); + resolvedHomeservers++; + StateHasChanged(); + } + StateHasChanged(); + } + _isLoaded = true; + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor new file mode 100644 index 0000000..7e9facf --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/Index.razor @@ -0,0 +1,33 @@ +@page "/" +@using MatrixRoomUtils.Web.Classes +@using MatrixRoomUtils.Web.Shared.IndexComponents +@using Blazored.LocalStorage +@inject NavigationManager NavigationManager +@inject ILocalStorageService LocalStorage + +<PageTitle>Index</PageTitle> + +<h1>Rory&::MatrixUtils</h1> +Small collection of tools to do not-so-everyday things. + +<br/><br/> +<h5>Signed in accounts - <a href="/Login">Add new account</a> or <a href="/ImportUsers">Import from TSV</a></h5> +<hr/> +@{ + Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count); + if (!RuntimeStorage.WasLoaded) + { + Console.WriteLine("[INDEX] !!! LOCALSTORAGE WAS NOT LOADED !!!"); + RuntimeStorage.LoadFromLocalStorage(LocalStorage).GetAwaiter().OnCompleted(() => + { + Console.WriteLine("Users in cache: " + RuntimeStorage.UsersCache.Count); + StateHasChanged(); + }); + } +} +<form> + @foreach (var (token, user) in RuntimeStorage.UsersCache) + { + <IndexUserItem User="@user" Token="@token"/> + } +</form> \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor new file mode 100644 index 0000000..d193f95 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/LoginPage.razor @@ -0,0 +1,46 @@ +@page "/Login" +@using MatrixRoomUtils.Authentication +@using MatrixRoomUtils.Web.Classes +@using Blazored.LocalStorage +@inject ILocalStorageService LocalStorage +<h3>Login</h3> + +<label>Homeserver:</label> +<input @bind="homeserver"/> +<br/> +<label>Username:</label> +<input @bind="username"/> +<br/> +<label>Password:</label> +<input @bind="password" type="password"/> +<br/> +<button @onclick="Login">Login</button> +<br/> +<br/> +<LogView></LogView> + +@code { + string homeserver = ""; + string username = ""; + string password = ""; + async Task Login() + { + var result = await MatrixAccount.Login(homeserver, username, password); + Console.WriteLine($"Obtained access token for {result.UserId}!"); + + RuntimeStorage.AccessToken = result.AccessToken; + + var userinfo = new UserInfo() + { + LoginResponse = result + }; + userinfo.Profile = await MatrixAccount.GetProfile(result.HomeServer, result.UserId); + + RuntimeStorage.UsersCache.Add(result.AccessToken, userinfo); + RuntimeStorage.CurrentHomeserver = await MatrixAccount.ResolveHomeserverFromWellKnown(result.HomeServer); + + + await RuntimeStorage.SaveToLocalStorage(LocalStorage); + + } +} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor b/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor new file mode 100644 index 0000000..cedaf32 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor @@ -0,0 +1,232 @@ +@page "/PolicyListEditor/{RoomId}" +@using MatrixRoomUtils.Authentication +@using MatrixRoomUtils.Web.Classes +@using Blazored.LocalStorage +@using System.Net.Http.Headers +@using System.Text.Json +@using MatrixRoomUtils.Extensions +@using MatrixRoomUtils.StateEventTypes +@inject ILocalStorageService LocalStorage +@inject NavigationManager NavigationManager +<h3>Policy list editor</h3> + +<p> + This policy list contains @PolicyEvents.Count(x => x.type == "m.policy.rule.server") server bans, + @PolicyEvents.Count(x => x.type == "m.policy.rule.room") room bans and + @PolicyEvents.Count(x => x.type == "m.policy.rule.user") user bans. +</p> + + +@if (!PolicyEvents.Any(x => x.type == "m.policy.rule.server")) +{ + <p>No server policies</p> +} +else +{ + <h3>Server policies</h3> + <hr/> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col" style="max-width: 50vw;">Server</th> + <th scope="col">Reason</th> + <th scope="col">Expires</th> + <th scope="col">Actions</th> + </tr> + </thead> + <tbody> + @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.server" && x.content.Entity != null)) + { + <tr> + <td>Entity: @policyEvent.content.Entity<br/>State: @policyEvent.state_key</td> + <td>@policyEvent.content.Reason</td> + <td> + @policyEvent.content.ExpiryDateTime + </td> + <td> + <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> + </td> + </tr> + } + </tbody> + </table> + <details> + <summary>Invalid events</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col" style="max-width: 50vw;">State key</th> + <th scope="col">Serialised contents</th> + </tr> + </thead> + <tbody> + @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.server" && x.content.Entity == null)) + { + <tr> + <td>@policyEvent.state_key</td> + <td>@policyEvent.content.ToJson(indent: false, ignoreNull: true)</td> + </tr> + } + </tbody> + </table> + </details> +} +@if (!PolicyEvents.Any(x => x.type == "m.policy.rule.room")) +{ + <p>No room policies</p> +} +else +{ + <h3>Room policies</h3> + <hr/> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col" style="max-width: 50vw;">Room</th> + <th scope="col">Reason</th> + <th scope="col">Expires</th> + <th scope="col">Actions</th> + </tr> + </thead> + <tbody> + @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.room" && x.content.Entity != null)) + { + <tr> + <td>Entity: @policyEvent.content.Entity<br/>State: @policyEvent.state_key</td> + <td>@policyEvent.content.Reason</td> + <td> + @policyEvent.content.ExpiryDateTime + </td> + <td> + <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> + </td> + </tr> + } + </tbody> + </table> + <details> + <summary>Invalid events</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col" style="max-width: 50vw;">State key</th> + <th scope="col">Serialised contents</th> + </tr> + </thead> + <tbody> + @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.room" && x.content.Entity == null)) + { + <tr> + <td>@policyEvent.state_key</td> + <td>@policyEvent.content.ToJson(indent: false, ignoreNull: true)</td> + </tr> + } + </tbody> + </table> + </details> +} +@if (!PolicyEvents.Any(x => x.type == "m.policy.rule.user")) +{ + <p>No user policies</p> +} +else +{ + <h3>User policies</h3> + <hr/> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col" style="max-width: 0.2vw; word-wrap: anywhere;">User</th> + <th scope="col">Reason</th> + <th scope="col">Expires</th> + <th scope="col">Actions</th> + </tr> + </thead> + <tbody> + @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.user" && x.content.Entity != null)) + { + <tr> + <td style="word-wrap: anywhere;">Entity: @string.Join("", policyEvent.content.Entity.Take(64))<br/>State: @string.Join("",policyEvent.state_key.Take(64))</td> + <td>@policyEvent.content.Reason</td> + <td> + @policyEvent.content.ExpiryDateTime + </td> + <td> + <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> + </td> + </tr> + } + </tbody> + </table> + <details> + <summary>Invalid events</summary> + <table class="table table-striped table-hover" style="width: fit-content;"> + <thead> + <tr> + <th scope="col">State key</th> + <th scope="col">Serialised contents</th> + </tr> + </thead> + <tbody> + @foreach (var policyEvent in PolicyEvents.Where(x => x.type == "m.policy.rule.user" && x.content.Entity == null)) + { + <tr> + <td>@policyEvent.state_key</td> + <td>@policyEvent.content.ToJson(indent: false, ignoreNull: true)</td> + </tr> + } + </tbody> + </table> + </details> +} + +<LogView></LogView> + +@code { + //get room list + // - sync withroom list filter + // type = support.feline.msc3784 + //support.feline.policy.lists.msc.v1 + + [Parameter] + public string? RoomId { get; set; } + + public List<StateEvent<PolicyRuleStateEventData>> PolicyEvents { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage); + await base.OnInitializedAsync(); + if(RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null) + { + NavigationManager.NavigateTo("/Login"); + return; + } + RoomId = RoomId.Replace('~', '.'); + await LoadStatesAsync(); + Console.WriteLine("Policy list editor initialized!"); + } + + private async Task LoadStatesAsync() + { + using var client = new HttpClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken); + var response = await client.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/r0/rooms/{RoomId}/state"); + var content = await response.Content.ReadAsStringAsync(); + // Console.WriteLine(JsonSerializer.Deserialize<object>(content).ToJson()); + var stateEvents = JsonSerializer.Deserialize<List<StateEvent>>(content); + PolicyEvents = stateEvents.Where(x => x.type.StartsWith("m.policy.rule")) + .Select(x => JsonSerializer.Deserialize<StateEvent<PolicyRuleStateEventData>>(JsonSerializer.Serialize(x))).ToList(); + StateHasChanged(); + // foreach (var stateEvent in PolicyEvents.Where(x => x.replaces_state != "" && x.replaces_state != null)) + // { + // Console.WriteLine($"{stateEvent.replaces_state} -> {PolicyEvents.Any(x => x.state_key == stateEvent.replaces_state)}"); + // } + // foreach (var policyEvent in PolicyEvents) + // { + // Console.WriteLine(policyEvent.ToJson()); + // } + } + +} + diff --git a/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor b/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor new file mode 100644 index 0000000..39b7087 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor @@ -0,0 +1,84 @@ +@page "/PolicyListEditor" +@using MatrixRoomUtils.Authentication +@using MatrixRoomUtils.Web.Classes +@using Blazored.LocalStorage +@using System.Net.Http.Headers +@using System.Text.Json +@using MatrixRoomUtils.Extensions +@using MatrixRoomUtils.StateEventTypes +@inject ILocalStorageService LocalStorage +@inject NavigationManager NavigationManager +<h3>Policy list editor</h3> + +<h5>Room list</h5> +<hr/> +@if (PolicyRoomList.Count == 0) +{ + <p>No policy rooms found.</p> +} +else +{ + foreach (var s in PolicyRoomList) + { + <a href="@(NavigationManager.Uri + "/" + s.Replace('.', '~'))">@s</a> + <br/> + } + <div style="margin-bottom: 4em;"></div> +} + +<LogView></LogView> + +@code { + //get room list + // - sync withroom list filter + // type = support.feline.msc3784 + //support.feline.policy.lists.msc.v1 + + public List<string> PolicyRoomList { get; set; } = new(); + public List<StateEvent<PolicyRuleStateEventData>> PolicyEvents { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + if (!RuntimeStorage.WasLoaded) await RuntimeStorage.LoadFromLocalStorage(LocalStorage); + await base.OnInitializedAsync(); + if(RuntimeStorage.AccessToken == null || RuntimeStorage.CurrentHomeserver == null) + { + NavigationManager.NavigateTo("/Login"); + return; + } + await EnumeratePolicyRooms(); + Console.WriteLine("Policy list editor initialized!"); + } + + private async Task EnumeratePolicyRooms() + { + using HttpClient wc = new(); + wc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", RuntimeStorage.AccessToken); + + //get room list + //temporary hack until rooms get enumerated... + string[] rooms = { "!fTjMjIzNKEsFlUIiru:neko.dev" }; + + foreach (string room in rooms) + { + Console.WriteLine($"Checking if {room} is a policy room..."); + var sk = await wc.GetAsync($"{RuntimeStorage.CurrentHomeserver}/_matrix/client/v3/rooms/{room}/state/org.matrix.mjolnir.shortcode"); + if (sk.IsSuccessStatusCode) + { + Console.WriteLine($"Got success..."); + var sko = await sk.Content.ReadFromJsonAsync<JsonElement>(); + if (sko.TryGetProperty("shortcode", out JsonElement shortcode)) + { + Console.WriteLine($"Room {room} has a shortcode: {shortcode.GetString()}!"); + PolicyRoomList.Add(room); + StateHasChanged(); + } + else Console.WriteLine("No record found..."); + } + else Console.WriteLine($"Got failure {sk.StatusCode}..."); + } + + //print to console + Console.WriteLine($"Detected policy lists: {PolicyRoomList.ToJson()}"); + } + } \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/UserImportPage.razor b/MatrixRoomUtils.Web/Pages/UserImportPage.razor new file mode 100644 index 0000000..ca0a213 --- /dev/null +++ b/MatrixRoomUtils.Web/Pages/UserImportPage.razor @@ -0,0 +1,74 @@ +@page "/ImportUsers" +@using MatrixRoomUtils.Authentication +@using MatrixRoomUtils.Web.Classes +@using Blazored.LocalStorage +@using System.Text.Json +@inject ILocalStorageService LocalStorage +<h3>Login</h3> + +<InputFile OnChange="@FileChanged"></InputFile> +<br/> +<button @onclick="Login">Login</button> +<br/><br/> +<h4>Parsed records</h4> +<hr/> +<table border="1"> + @foreach (var (homeserver, username, password) in records) + { + <tr style="background-color: @(RuntimeStorage.UsersCache.Any(x => x.Value.LoginResponse.UserId == $"@{username}:{homeserver}") ? "green" : "unset")"> + <td style="border-width: 1px;">@username</td> + <td style="border-width: 1px;">@homeserver</td> + <td style="border-width: 1px;">@password.Length chars</td> + </tr> + } +</table> +<br/> +<br/> +<LogView></LogView> + +@code { + List<(string homeserver, string username, string password)> records = new(); + + async Task Login() + { + foreach (var (homeserver, username, password) in records) + { + if(RuntimeStorage.UsersCache.Any(x => x.Value.LoginResponse.UserId == $"@{username}:{homeserver}")) continue; + var result = await MatrixAccount.Login(homeserver, username, password); + Console.WriteLine($"Obtained access token for {result.UserId}!"); + + RuntimeStorage.AccessToken = result.AccessToken; + + var userinfo = new UserInfo() + { + LoginResponse = result + }; + userinfo.Profile = await MatrixAccount.GetProfile(result.HomeServer, result.UserId); + + RuntimeStorage.UsersCache.Add(result.AccessToken, userinfo); + StateHasChanged(); + } + + await RuntimeStorage.SaveToLocalStorage(LocalStorage); + } + + private async Task FileChanged(InputFileChangeEventArgs obj) + { + Console.WriteLine(JsonSerializer.Serialize(obj, new JsonSerializerOptions() + { + WriteIndented = true + })); + await using var rs = obj.File.OpenReadStream(); + using var sr = new StreamReader(rs); + string TsvData = await sr.ReadToEndAsync(); + records.Clear(); + foreach (var line in TsvData.Split('\n')) + { + var parts = line.Split('\t'); + if (parts.Length != 3) + continue; + records.Add((parts[0], parts[1], parts[2])); + } + } + +} \ No newline at end of file |