diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor
index b1a52d5..4be91b7 100644
--- a/MatrixUtils.Web/Pages/Index.razor
+++ b/MatrixUtils.Web/Pages/Index.razor
@@ -168,17 +168,11 @@ Small collection of tools to do not-so-everyday things.
}
List<string> offlineServers = [];
- var sema = new SemaphoreSlim(64, 64);
+ var sema = new SemaphoreSlim(8, 8);
var updateSw = Stopwatch.StartNew();
var tasks = tokens.Select(async token => {
await sema.WaitAsync();
- if ((!string.IsNullOrWhiteSpace(token.Proxy) && offlineServers.Contains(token.Proxy)) || offlineServers.Contains(token.Homeserver)) {
- _offlineSessions.Add(token);
- sema.Release();
- scannedSessions++;
- return;
- }
-
+
AuthenticatedHomeserverGeneric hs;
try {
hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy);
@@ -187,7 +181,7 @@ Small collection of tools to do not-so-everyday things.
var serverVersionTask = hs.FederationClient?.GetServerVersionAsync();
_sessions.Add(new() {
UserInfo = new() {
- AvatarUrl = (await profileTask).AvatarUrl ?? "meow",
+ AvatarUrl = (await profileTask).AvatarUrl,
RoomCount = (await joinedRoomsTask).Count,
DisplayName = (await profileTask).DisplayName ?? hs.WhoAmI.UserId
},
diff --git a/MatrixUtils.Web/Pages/LoginPage.razor b/MatrixUtils.Web/Pages/LoginPage.razor
index 6c869ac..d43913c 100644
--- a/MatrixUtils.Web/Pages/LoginPage.razor
+++ b/MatrixUtils.Web/Pages/LoginPage.razor
@@ -27,6 +27,27 @@
<br/>
<br/>
+
+<h4>Add with access token</h4>
+<hr/>
+
+<span style="display: block;">
+ <label>Homeserver:</label>
+ <FancyTextBox @bind-Value="@newRecordInput.Homeserver"></FancyTextBox>
+</span>
+<span style="display: block;">
+ <label>Access token:</label>
+ <FancyTextBox @bind-Value="@newRecordInput.Password" IsPassword="true"></FancyTextBox>
+</span>
+<span style="display: block">
+ <label>Proxy (<a href="https://cgit.rory.gay/matrix/MxApiExtensions.git">MxApiExtensions</a> or similar):</label>
+ <FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox>
+</span>
+<br/>
+<LinkButton OnClick="@(() => AddWithAccessToken(newRecordInput))">Add session</LinkButton>
+<br/>
+<br/>
+
<h4>Import from TSV</h4>
<hr/>
<span>Import credentials from a TSV (Tab Separated Values) file</span><br/>
@@ -156,4 +177,27 @@
internal Exception? Exception { get; set; }
}
+ private async Task AddWithAccessToken(LoginStruct record) {
+ try {
+ var session = await hsProvider.GetAuthenticatedWithToken(record.Homeserver, record.Password, record.Proxy);
+ if (session == null) {
+ Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!");
+ return;
+ }
+
+ await RMUStorage.AddToken(new UserAuth() {
+ UserId = session.WhoAmI.UserId,
+ AccessToken = session.AccessToken,
+ Proxy = record.Proxy,
+ DeviceId = session.WhoAmI.DeviceId
+ });
+ LoggedInSessions = await RMUStorage.GetAllTokens();
+ }
+ catch (Exception e) {
+ Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!");
+ Console.WriteLine(e);
+ record.Exception = e;
+ }
+ }
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index 4dc2adc..d57aa43 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -214,13 +214,13 @@ else {
private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name;
private async Task RemovePolicyAsync(StateEventResponse policyEvent) {
- await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), new { });
+ await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, new { });
PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent);
await LoadStatesAsync();
}
private async Task UpdatePolicyAsync(StateEventResponse policyEvent) {
- await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), policyEvent.RawContent);
+ await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent);
CurrentlyEditingEvent = null;
await LoadStatesAsync();
}
diff --git a/MatrixUtils.Web/Pages/Tools/Index.razor b/MatrixUtils.Web/Pages/Tools/Index.razor
index e68bb9a..f99e932 100644
--- a/MatrixUtils.Web/Pages/Tools/Index.razor
+++ b/MatrixUtils.Web/Pages/Tools/Index.razor
@@ -24,7 +24,7 @@
<a href="/Tools/Moderation/UserTrace">Trace user across rooms</a><br/>
<a href="/tools/Moderation/MassCMEBan">Mass write policies to Community Moderation Effort</a><br/>
<a href="/tools/Moderation/RoomIntersections">Find rooms with common users</a><br/>
-<a href="/tools/Moderation/DraupnirProtectedRoomsEditor">Edit Draupnir protected rooms set</a><br/>
+<a href="/tools/Moderation/Draupnir/ProtectedRoomsEditor">Draupnir: edit protected rooms set</a><br/>
<h4 class="tool-category">Debugging tools</h4>
diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
index de0bfe7..f2af9b5 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
@@ -5,6 +5,8 @@
@using LibMatrix.EventTypes.Common
+
+@* <ActivityGraph Data="TestData"/> *@
@if (RoomData.Count == 0)
{
<p>Loading...</p>
@@ -15,10 +17,8 @@ else
<h3>@room.Key</h3>
@foreach (var year in room.Value.OrderBy(x => x.Key))
{
- <h5>@year.Key</h5>
- <ActivityGraph Data="@year.Value" GlobalMax="MaxValue"
- RLabel="removed" GLabel="new" BLabel="updated policies">
- </ActivityGraph>
+ <span>@year.Key</span>
+ <ActivityGraph Data="@year.Value" GlobalMax="MaxValue" RLabel="removed" GLabel="new" BLabel="updated policies"/>
}
}
@@ -42,15 +42,22 @@ else
await base.OnInitializedAsync();
Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!;
if (Homeserver is null) return;
-
+ //
//random test data
- for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(Random.Shared.Next(5)))
+ for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(2))
{
+ // TestData[i] = new()
+ // {
+ // R = (int)(Random.Shared.NextSingle() * 255),
+ // G = (int)(Random.Shared.NextSingle() * 255),
+ // B = (int)(Random.Shared.NextSingle() * 255)
+ // };
+ // rgb based on number of week
TestData[i] = new()
{
- R = (int)(Random.Shared.NextSingle() * 255),
- G = (int)(Random.Shared.NextSingle() * 255),
- B = (int)(Random.Shared.NextSingle() * 255)
+ R = i.DayOfYear % 255,
+ G = i.DayOfYear + 96 % 255,
+ B = i.DayOfYear + 192 % 255
};
}
@@ -109,7 +116,7 @@ else
}
//use timeline
- var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000);
+ var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 2000);
await foreach (var response in timeline)
{
Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline");
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor
new file mode 100644
index 0000000..8745459
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor
@@ -0,0 +1,143 @@
+@page "/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/Draupnir/ProtectedRoomsEditor"
+@using System.Text.Json.Serialization
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.RoomTypes
+<h3>Edit Draupnir protected rooms</h3>
+<hr/>
+<p><b>Note:</b> You will need to restart Draupnir after applying changes!</p>
+<p>Minor note: This <i>should</i> also work with Mjolnir, but this hasn't been tested, and as such functionality cannot be guaranteed.</p>
+
+@if (data is not null) {
+ <div class="row">
+ <div class="col-12">
+ <details>
+ <summary>Currently protected room IDs</summary>
+ <ul>
+ @foreach (var room in data.Rooms) {
+ <li>@room</li>
+ }
+ </ul>
+ </details>
+ <hr/>
+ <h4>Tickyboxes</h4>
+ <table class="table">
+ <thead>
+ <tr>
+ <th></th> @* Checkbox column *@
+ <th>Kick?</th> @* PL > kick *@
+ <th>Ban?</th> @* PL > ban *@
+ <th>ACL?</th> @* PL > m.room.server_acls event *@
+ <th>Room ID</th>
+ <th>Room name</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var room in Rooms.OrderBy(x => x.RoomName)) {
+ <tr>
+ <td>
+ <input type="checkbox" @bind="room.IsProtected"/>
+ </td>
+ <td>@(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td>
+ <td>@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td>
+ <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerACLEventContent.EventId) ? "X" : "")</td>
+ <td>@room.Room.RoomId</td>
+ <td>@room.RoomName</td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
+}
+<br/>
+<LinkButton OnClick="@Apply">Apply</LinkButton>
+
+
+@code {
+ private DraupnirProtectedRoomsData data { get; set; } = new();
+ private List<EditorRoomInfo> Rooms { get; set; } = new();
+ private AuthenticatedHomeserverGeneric hs { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+ data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>("org.matrix.mjolnir.protected_rooms");
+ StateHasChanged();
+ var tasks = (await hs.GetJoinedRooms()).Select(async room => {
+ var plTask = room.GetPowerLevelsAsync();
+ var roomNameTask = room.GetNameOrFallbackAsync();
+ var EditorRoomInfo = new EditorRoomInfo {
+ Room = room,
+ IsProtected = data.Rooms.Contains(room.RoomId),
+ RoomName = await roomNameTask,
+ PowerLevels = await plTask
+ };
+
+ Rooms.Add(EditorRoomInfo);
+ StateHasChanged();
+ return Task.CompletedTask;
+ }).ToList();
+ await Task.WhenAll(tasks);
+ await Task.Delay(500);
+
+ foreach (var protectedRoomId in data.Rooms) {
+ if (Rooms.Any(x => x.Room.RoomId == protectedRoomId)) continue;
+ var room = hs.GetRoom(protectedRoomId);
+ var editorRoomInfo = new EditorRoomInfo {
+ Room = room,
+ IsProtected = true
+ };
+
+ try {
+ var pl = await room.GetPowerLevelsAsync();
+ editorRoomInfo.PowerLevels = pl;
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get power levels for {room.RoomId}: {e}");
+ }
+
+ try {
+ editorRoomInfo.RoomName = await room.GetNameOrFallbackAsync();
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get name for {room.RoomId}: {e}");
+ }
+
+ try {
+ var membership = await room.GetStateEventOrNullAsync(hs.UserId);
+ if (membership is not null) {
+ editorRoomInfo.RoomName = $"(!! {membership.ContentAs<RoomMemberEventContent>()?.Membership ?? "null"} !!) {editorRoomInfo.RoomName}";
+ }
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get membership for {room.RoomId}: {e}");
+ }
+
+ Rooms.Add(editorRoomInfo);
+ }
+
+ StateHasChanged();
+ }
+
+ private class DraupnirProtectedRoomsData {
+ [JsonPropertyName("rooms")]
+ public List<string> Rooms { get; set; } = new();
+ }
+
+ private class EditorRoomInfo {
+ public GenericRoom Room { get; set; }
+ public bool IsProtected { get; set; }
+ public string RoomName { get; set; }
+ public RoomPowerLevelEventContent PowerLevels { get; set; }
+ }
+
+ private async Task Apply() {
+ Console.WriteLine(string.Join('\n', Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId)));
+ data.Rooms = Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId).ToList();
+ await hs.SetAccountDataAsync("org.matrix.mjolnir.protected_rooms", data);
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor
index 805bd40..b722596 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor
@@ -1,6 +1,6 @@
-@page "/Moderation/DraupnirProtectedRoomsEditor"
-@page "/Tools/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/Draupnir/ProtectionsEditor"
@using System.Text.Json.Serialization
+@using LibMatrix
@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.RoomTypes
<h3>Edit Draupnir protected rooms</h3>
@@ -78,6 +78,43 @@
}).ToList();
await Task.WhenAll(tasks);
await Task.Delay(500);
+
+ foreach (var protectedRoomId in data.Rooms) {
+ if (Rooms.Any(x => x.Room.RoomId == protectedRoomId)) continue;
+ var room = hs.GetRoom(protectedRoomId);
+ var editorRoomInfo = new EditorRoomInfo {
+ Room = room,
+ IsProtected = true
+ };
+
+ try {
+ var pl = await room.GetPowerLevelsAsync();
+ editorRoomInfo.PowerLevels = pl;
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get power levels for {room.RoomId}: {e}");
+ }
+
+ try {
+ editorRoomInfo.RoomName = await room.GetNameOrFallbackAsync();
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get name for {room.RoomId}: {e}");
+ }
+
+ try {
+ var membership = await room.GetStateEventOrNullAsync(hs.UserId);
+ if (membership is not null) {
+ editorRoomInfo.RoomName = $"(!! {membership.ContentAs<RoomMemberEventContent>()?.Membership ?? "null"} !!) {editorRoomInfo.RoomName}";
+ }
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get membership for {room.RoomId}: {e}");
+ }
+
+ Rooms.Add(editorRoomInfo);
+ }
+
StateHasChanged();
}
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor
new file mode 100644
index 0000000..b2f4026
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor
@@ -0,0 +1,139 @@
+@page "/Tools/Moderation/Draupnir/WatchedListsEditor"
+@using System.Text.Json.Serialization
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.RoomTypes
+<h3>Edit Draupnir protected rooms</h3>
+<hr/>
+<p><b>Note:</b> You will need to restart Draupnir after applying changes!</p>
+<p>Minor note: This <i>should</i> also work with Mjolnir, but this hasn't been tested, and as such functionality cannot be guaranteed.</p>
+
+@if (data is not null) {
+ <div class="row">
+ <div class="col-12">
+ <h4>Current rooms</h4>
+ <ul>
+ @foreach (var room in data.Rooms) {
+ <li>@room</li>
+ }
+ </ul>
+ <hr/>
+ <h4>Tickyboxes</h4>
+ <table class="table">
+ <thead>
+ <tr>
+ <th></th> @* Checkbox column *@
+ <th>Kick?</th> @* PL > kick *@
+ <th>Ban?</th> @* PL > ban *@
+ <th>ACL?</th> @* PL > m.room.server_acls event *@
+ <th>Room ID</th>
+ <th>Room name</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var room in Rooms.OrderBy(x => x.RoomName)) {
+ <tr>
+ <td>
+ <input type="checkbox" @bind="room.IsProtected"/>
+ </td>
+ <td>@(room.PowerLevels.Kick <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td>
+ <td>@(room.PowerLevels.Ban <= room.PowerLevels.GetUserPowerLevel(hs.UserId) ? "X" : "")</td>
+ <td>@(room.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerACLEventContent.EventId) ? "X" : "")</td>
+ <td>@room.Room.RoomId</td>
+ <td>@room.RoomName</td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
+}
+<br/>
+<LinkButton OnClick="@Apply">Apply</LinkButton>
+
+
+@code {
+ private DraupnirProtectedRoomsData data { get; set; } = new();
+ private List<EditorRoomInfo> Rooms { get; set; } = new();
+ private AuthenticatedHomeserverGeneric hs { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+ data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>("org.matrix.mjolnir.protected_rooms");
+ StateHasChanged();
+ var tasks = (await hs.GetJoinedRooms()).Select(async room => {
+ var plTask = room.GetPowerLevelsAsync();
+ var roomNameTask = room.GetNameOrFallbackAsync();
+ var EditorRoomInfo = new EditorRoomInfo {
+ Room = room,
+ IsProtected = data.Rooms.Contains(room.RoomId),
+ RoomName = await roomNameTask,
+ PowerLevels = await plTask
+ };
+
+ Rooms.Add(EditorRoomInfo);
+ StateHasChanged();
+ return Task.CompletedTask;
+ }).ToList();
+ await Task.WhenAll(tasks);
+ await Task.Delay(500);
+
+ foreach (var protectedRoomId in data.Rooms) {
+ if (Rooms.Any(x => x.Room.RoomId == protectedRoomId)) continue;
+ var room = hs.GetRoom(protectedRoomId);
+ var editorRoomInfo = new EditorRoomInfo {
+ Room = room,
+ IsProtected = true
+ };
+
+ try {
+ var pl = await room.GetPowerLevelsAsync();
+ editorRoomInfo.PowerLevels = pl;
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get power levels for {room.RoomId}: {e}");
+ }
+
+ try {
+ editorRoomInfo.RoomName = await room.GetNameOrFallbackAsync();
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get name for {room.RoomId}: {e}");
+ }
+
+ try {
+ var membership = await room.GetStateEventOrNullAsync(hs.UserId);
+ if (membership is not null) {
+ editorRoomInfo.RoomName = $"(!! {membership.ContentAs<RoomMemberEventContent>()?.Membership ?? "null"} !!) {editorRoomInfo.RoomName}";
+ }
+ }
+ catch (MatrixException e) {
+ Console.WriteLine($"Failed to get membership for {room.RoomId}: {e}");
+ }
+
+ Rooms.Add(editorRoomInfo);
+ }
+
+ StateHasChanged();
+ }
+
+ private class DraupnirProtectedRoomsData {
+ [JsonPropertyName("rooms")]
+ public List<string> Rooms { get; set; } = new();
+ }
+
+ private class EditorRoomInfo {
+ public GenericRoom Room { get; set; }
+ public bool IsProtected { get; set; }
+ public string RoomName { get; set; }
+ public RoomPowerLevelEventContent PowerLevels { get; set; }
+ }
+
+ private async Task Apply() {
+ Console.WriteLine(string.Join('\n', Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId)));
+ data.Rooms = Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId).ToList();
+ await hs.SetAccountDataAsync("org.matrix.mjolnir.protected_rooms", data);
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor
index d8b02bb..153518e 100644
--- a/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor
@@ -1,4 +1,4 @@
-@page "/Tools/ViewAccountData"
+@page "/Tools/User/ViewAccountData"
@using ArcaneLibs.Extensions
@using LibMatrix
<h3>View account data</h3>
|