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/DataExportPage.razor83
-rw-r--r--MatrixRoomUtils.Web/Pages/Index.razor33
-rw-r--r--MatrixRoomUtils.Web/Pages/LoginPage.razor46
-rw-r--r--MatrixRoomUtils.Web/Pages/PolicyListEditorPage.razor232
-rw-r--r--MatrixRoomUtils.Web/Pages/PolicyListRoomList.razor84
-rw-r--r--MatrixRoomUtils.Web/Pages/UserImportPage.razor74
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