diff --git a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
index 841552e..9a56fc0 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor
@@ -17,7 +17,7 @@
public string? RoomId { get; set; }
protected override async Task OnInitializedAsync() {
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
Log.CollectionChanged += (sender, args) => StateHasChanged();
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor
index 6e87926..dd8a801 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/MediaLocator.razor
@@ -92,7 +92,7 @@
lines.ToList().ForEach(async line => {
await sem.WaitAsync();
try {
- homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client);
+ homeservers.Add((await HsResolver.ResolveHomeserverFromWellKnown(line)).Client);
StateHasChanged();
}
catch (Exception e) {
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
index 11d35f1..0943216 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor
@@ -39,7 +39,7 @@
private string newRoomId { get; set; }
protected override async Task OnInitializedAsync() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
StateHasChanged();
@@ -48,7 +48,7 @@
}
private async Task Execute() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
var oldRoom = hs.GetRoom(roomId);
var newRoom = hs.GetRoom(newRoomId);
@@ -90,7 +90,7 @@
private async Task TryFetchUsers() {
try {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
var room = hs.GetRoom(roomId);
var members = await room.GetMembersListAsync();
diff --git a/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor
index 263879b..7abb3d2 100644
--- a/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor
+++ b/MatrixUtils.Web/Pages/Tools/Debug/SpaceDebug.razor
@@ -45,7 +45,7 @@
protected override async Task OnInitializedAsync() {
Status = "Getting homeserver...";
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
var syncHelper = new SyncHelper(hs) {
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/KnownHomeserverList.razor b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
index ddd7b15..acad827 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/KnownHomeserverList.razor
@@ -1,45 +1,73 @@
-@page "/Tools/KnownHomeserverList"
+@page "/Tools/Info/KnownHomeserverList"
@using ArcaneLibs.Extensions
+@using LibMatrix.RoomTypes
+@using SpawnDev.BlazorJS.WebWorkers
+@inject WebWorkerService workerService
<h3>Known Homeserver List</h3>
<hr/>
@if (!IsFinished) {
<p>
- <b>Loading...</b>
+ <b>Loading... @RoomCount rooms remaining to process...</b>
</p>
}
-@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) {
- <p>@homeserver - @members</p>
+@{
+ var shownCounts = counts.OrderByDescending(x => x.Value).AsEnumerable();
+ if (!IsFinished && counts.Count > 500) {
+ shownCounts = shownCounts.Where(x => x.Value > 5);
+ }
+}
+@foreach (var (homeserver, members) in shownCounts.ToList()) {
+ <p>@homeserver - @members users</p>
}
<hr/>
@code {
Dictionary<string, List<string>> homeservers { get; set; } = new();
+
Dictionary<string, int> counts { get; set; } = new();
+
// List<HomeserverInfo> Homeservers = new();
bool IsFinished { get; set; }
+
// HomeserverInfoQueryProgress QueryProgress { get; set; } = new();
AuthenticatedHomeserverGeneric? hs { get; set; }
+ int RoomCount { get; set; } = 0;
protected override async Task OnInitializedAsync() {
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
- var fetchTasks = (await hs.GetJoinedRooms()).Select(x=>x.GetMembersByHomeserverAsync()).ToAsyncEnumerable();
+ var ss = new SemaphoreSlim(32, 32);
+ var rooms = await hs.GetJoinedRooms();
+ RoomCount = rooms.Count;
+ var fetchTasks = rooms.Select(roomId => workerService.TaskPool.Invoke(() => InternalGetMembersByHomeserver(hs.WellKnownUris.Client, hs.AccessToken, roomId.RoomId))).ToList().ToAsyncEnumerable();
+ // var fetchTasks = rooms.Select(async x => {
+ // await ss.WaitAsync();
+ // var res = await x.GetMembersByHomeserverAsync();
+ // ss.Release();
+ // return res;
+ // }).ToAsyncEnumerable();
await foreach (var result in fetchTasks) {
foreach (var (resHomeserver, resMembers) in result) {
if (!homeservers.TryAdd(resHomeserver, resMembers)) {
homeservers[resHomeserver].AddRange(resMembers);
}
+
counts[resHomeserver] = homeservers[resHomeserver].Count;
}
- // StateHasChanged();
+
+ RoomCount--;
+ StateHasChanged();
// await Task.Delay(250);
+ await Task.Yield();
}
foreach (var resHomeserver in homeservers.Keys) {
homeservers[resHomeserver] = homeservers[resHomeserver].Distinct().ToList();
counts[resHomeserver] = homeservers[resHomeserver].Count;
+ StateHasChanged();
+ await Task.Yield();
}
IsFinished = true;
@@ -48,4 +76,10 @@
await base.OnInitializedAsync();
}
+ private static async Task<Dictionary<string, List<string>>> InternalGetMembersByHomeserver(string homeserverBaseUrl, string accessToken, string roomId) {
+ var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken);
+ var room = hs.GetRoom(roomId);
+ return await room.GetMembersByHomeserverAsync();
+ }
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
index de0bfe7..f8d1d31 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/PolicyListActivity.razor
@@ -1,24 +1,24 @@
@page "/Tools/Info/PolicyListActivity"
@using LibMatrix.EventTypes.Spec.State.Policy
@using System.Diagnostics
+@using System.Reflection
+@using ArcaneLibs.Extensions
+@using LibMatrix
+@using LibMatrix.EventTypes
@using LibMatrix.RoomTypes
@using LibMatrix.EventTypes.Common
+@using LibMatrix.Filters
-
-@if (RoomData.Count == 0)
-{
+@* <ActivityGraph Data="TestData"/> *@
+@if (RoomData.Count == 0) {
<p>Loading...</p>
}
else
- foreach (var room in RoomData)
- {
+ foreach (var room in RoomData) {
<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>
+ @foreach (var year in room.Value.OrderBy(x => x.Key)) {
+ <span>@year.Key</span>
+ <ActivityGraph Data="@year.Value" GlobalMax="MaxValue" RLabel="removed" GLabel="new" BLabel="updated policies"/>
}
}
@@ -29,59 +29,26 @@ else
public Dictionary<DateOnly, ActivityGraph.RGB> TestData { get; set; } = new();
- public ActivityGraph.RGB MaxValue { get; set; } = new()
- {
+ public ActivityGraph.RGB MaxValue { get; set; } = new() {
R = 255, G = 255, B = 255
};
public Dictionary<string, Dictionary<int, Dictionary<DateOnly, ActivityGraph.RGB>>> RoomData { get; set; } = new();
- protected override async Task OnInitializedAsync()
- {
+ protected override async Task OnInitializedAsync() {
var sw = Stopwatch.StartNew();
await base.OnInitializedAsync();
- Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!;
+ Homeserver = (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!;
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)))
- {
- TestData[i] = new()
- {
- R = (int)(Random.Shared.NextSingle() * 255),
- G = (int)(Random.Shared.NextSingle() * 255),
- B = (int)(Random.Shared.NextSingle() * 255)
- };
- }
-
- StateHasChanged();
- // return;
-
var rooms = await Homeserver.GetJoinedRooms();
- // foreach (var room in rooms)
- // {
- // var type = await room.GetRoomType();
- // if (type == "support.feline.policy.lists.msc.v1")
- // {
- // Console.WriteLine($"{room.RoomId} is policy list by type");
- // FilteredRooms.Add(room);
- // }
- // else if(await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId) is not null)
- // {
- // Console.WriteLine($"{room.RoomId} is policy list by shortcode");
- // FilteredRooms.Add(room);
- // }
- // }
- var roomFilterTasks = rooms.Select(async room =>
- {
+ var roomFilterTasks = rooms.Select(async room => {
var type = await room.GetRoomType();
- if (type == "support.feline.policy.lists.msc.v1")
- {
+ if (type == "support.feline.policy.lists.msc.v1") {
Console.WriteLine($"{room.RoomId} is policy list by type");
return room;
}
- else if (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId) is not null)
- {
+ else if (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId) is not null) {
Console.WriteLine($"{room.RoomId} is policy list by shortcode");
return room;
}
@@ -99,60 +66,75 @@ else
Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms");
}
- public async Task FetchRoomHistory(GenericRoom room)
- {
+ public async Task FetchRoomHistory(GenericRoom room) {
var roomName = await room.GetNameOrFallbackAsync();
- if (string.IsNullOrWhiteSpace(roomName)) roomName = room.RoomId;
- if (!RoomData.ContainsKey(roomName))
- {
- RoomData[roomName] = new();
- }
+ if (string.IsNullOrWhiteSpace(roomName)) roomName = room.RoomId;
+ if (!RoomData.ContainsKey(roomName)) {
+ RoomData[roomName] = new();
+ }
+
+ //use timeline
+ var types = StateEventResponse.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent)));
+ var filter = new SyncFilter.EventFilter(types: types.SelectMany(x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName)).ToList());
+ var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 2500, filter: filter.ToJson(indent: false, ignoreNull: true));
+ await foreach (var response in timeline) {
+ Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline");
+ if (response.State.Count != 0) throw new Exception("Why the hell did we receive state events?");
+ foreach (var message in response.Chunk) {
+ if (!message.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
+ //OriginServerTs to datetime
+ var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)message.OriginServerTs!.Value).DateTime;
+ var date = new DateOnly(dt.Year, dt.Month, dt.Day);
+ if (!RoomData[roomName].ContainsKey(date.Year)) {
+ RoomData[roomName][date.Year] = new();
+ }
- //use timeline
- var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000);
- await foreach (var response in timeline)
- {
- Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline");
- if (response.State.Count != 0) throw new Exception("Why the hell did we receive state events?");
- foreach (var message in response.Chunk)
- {
- if (!message.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
- //OriginServerTs to datetime
- var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)message.OriginServerTs!.Value).DateTime;
- var date = new DateOnly(dt.Year, dt.Month, dt.Day);
- if (!RoomData[roomName].ContainsKey(date.Year))
- {
- RoomData[roomName][date.Year] = new();
- }
-
- if (!RoomData[roomName][date.Year].ContainsKey(date))
- {
- // Console.WriteLine($"Adding {date} to {roomName}");
- RoomData[roomName][date.Year][date] = new();
- }
-
- var rgb = RoomData[roomName][date.Year][date];
- if (message.RawContent?.Count == 0) rgb.R++;
- else if (string.IsNullOrWhiteSpace(message.Unsigned?.ReplacesState)) rgb.G++;
- else rgb.B++;
- RoomData[roomName][date.Year][date] = rgb;
+ if (!RoomData[roomName][date.Year].ContainsKey(date)) {
+ // Console.WriteLine($"Adding {date} to {roomName}");
+ RoomData[roomName][date.Year][date] = new();
}
- var max = RoomData.SelectMany(x => x.Value.Values).Aggregate(new ActivityGraph.RGB(), (current, next) => new()
- {
- R = Math.Max(current.R, next.Average(x => x.Value.R)),
- G = Math.Max(current.G, next.Average(x => x.Value.G)),
- B = Math.Max(current.B, next.Average(x => x.Value.B))
- });
- MaxValue = new ActivityGraph.RGB(
- r: Math.Max(max.R, Math.Max(max.G, max.B)),
- g: Math.Max(max.R, Math.Max(max.G, max.B)),
- b: Math.Max(max.R, Math.Max(max.G, max.B)));
- Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}");
- StateHasChanged();
- await Task.Delay(100);
+ var rgb = RoomData[roomName][date.Year][date];
+ if (message.RawContent is { Count: 0 } or null) rgb.R++;
+ else if (!message.Unsigned?.ContainsKey("replaces_state") ?? true) rgb.G++;
+ else rgb.B++;
+ RoomData[roomName][date.Year][date] = rgb;
}
+
+
+ }
+ var max = RoomData.SelectMany(x => x.Value.Values).Aggregate(new ActivityGraph.RGB(), (current, next) => new() {
+ R = Math.Max(current.R, next.Average(x => x.Value.R)),
+ G = Math.Max(current.G, next.Average(x => x.Value.G)),
+ B = Math.Max(current.B, next.Average(x => x.Value.B))
+ });
+ MaxValue = new ActivityGraph.RGB(
+ r: Math.Max(max.R, Math.Max(max.G, max.B)),
+ g: Math.Max(max.R, Math.Max(max.G, max.B)),
+ b: Math.Max(max.R, Math.Max(max.G, max.B)));
+ Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}");
+ StateHasChanged();
+ await Task.Yield();
}
+ private readonly struct StateEventEntry {
+ public required DateTime Timestamp { get; init; }
+ public required StateEventTransition State { get; init; }
+ public required StateEventResponse Event { get; init; }
+ public required StateEventResponse? Previous { get; init; }
+
+ public void Deconstruct(out StateEventTransition transition, out StateEventResponse evt, out StateEventResponse? prev) {
+ transition = State;
+ evt = Event;
+ prev = Previous;
+ }
+ }
+
+ private enum StateEventTransition : byte {
+ None,
+ Add,
+ Update,
+ Remove
+ }
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
index 3b68bfa..ce3513b 100644
--- a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
+++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor
@@ -4,7 +4,7 @@
@using System.Collections.ObjectModel
@using LibMatrix
@using System.Collections.Frozen
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
<h3>User Trace</h3>
<hr/>
@@ -73,20 +73,20 @@
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
rooms.CollectionChanged += (sender, args) => StateHasChanged();
- var sessions = await RMUStorage.GetAllTokens();
+ var sessions = await sessionStore.GetAllSessions();
foreach (var userAuth in sessions) {
- var session = await RMUStorage.GetSession(userAuth);
- if (session is not null) {
- var sessionRooms = await session.GetJoinedRooms();
+ var homeserver = await sessionStore.GetHomeserver(userAuth.Key);
+ if (homeserver is not null) {
+ var sessionRooms = await homeserver.GetJoinedRooms();
foreach (var room in sessionRooms) {
rooms.Add(room);
}
StateHasChanged();
- log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}");
+ log.Add($"Got {sessionRooms.Count} rooms for {userAuth.Value.Auth.UserId}");
}
}
@@ -106,7 +106,7 @@
log.Add($"Done fetching members!");
- UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x));
+ UserIDs.RemoveAll(x => sessions.Any(y => y.Value.Auth.UserId == x));
StateHasChanged();
Console.WriteLine("Rerendered!");
diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
index 8f4b4dd..2313884 100644
--- a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
+++ b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor
@@ -1,11 +1,8 @@
@page "/Tools/InviteCounter"
-@using ArcaneLibs.Extensions
-@using LibMatrix.RoomTypes
@using System.Collections.ObjectModel
-@using LibMatrix
-@using System.Collections.Frozen
-@using LibMatrix.EventTypes.Spec.State
-@using MatrixUtils.Abstractions
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Filters
<h3>User Trace</h3>
<hr/>
@@ -18,7 +15,7 @@
<details>
<summary>Results</summary>
- @foreach (var (userId, events) in invites.OrderByDescending(x=>x.Value).ToList()) {
+ @foreach (var (userId, events) in invites.OrderByDescending(x => x.Value).ToList()) {
<p>@userId: @events</p>
}
</details>
@@ -32,16 +29,15 @@
private ObservableCollection<string> log { get; set; } = new();
private Dictionary<string, int> invites { get; set; } = new();
private AuthenticatedHomeserverGeneric hs { get; set; }
-
+
[Parameter, SupplyParameterFromQuery(Name = "room")]
public string roomId { get; set; }
-
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
-
+
StateHasChanged();
Console.WriteLine("Rerendered!");
await base.OnInitializedAsync();
@@ -49,22 +45,21 @@
private async Task<string> Execute() {
var room = hs.GetRoom(roomId);
- var events = room.GetManyMessagesAsync(limit: int.MaxValue);
+ var filter = new SyncFilter.EventFilter(types: ["m.room.member"]);
+ var events = room.GetManyMessagesAsync(limit: int.MaxValue, filter: filter.ToJson(indent: false, ignoreNull: true));
await foreach (var resp in events) {
var all = resp.State.Concat(resp.Chunk);
foreach (var evt in all) {
- if(evt.Type != RoomMemberEventContent.EventId) continue;
+ if (evt.Type != RoomMemberEventContent.EventId) continue;
var content = evt.TypedContent as RoomMemberEventContent;
- if(content.Membership != "invite") continue;
- if(!invites.ContainsKey(evt.Sender)) invites[evt.Sender] = 0;
+ if (content.Membership != "invite") continue;
+ if (!invites.ContainsKey(evt.Sender)) invites[evt.Sender] = 0;
invites[evt.Sender]++;
}
log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline");
}
-
-
-
+
StateHasChanged();
return "";
diff --git a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
index cbbca9e..a252e6b 100644
--- a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
+++ b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor
@@ -1,12 +1,6 @@
@page "/Tools/MassCMEBan"
-@using ArcaneLibs.Extensions
-@using LibMatrix.RoomTypes
@using System.Collections.ObjectModel
-@using LibMatrix
-@using System.Collections.Frozen
-@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.EventTypes.Spec.State.Policy
-@using MatrixUtils.Abstractions
<h3>User Trace</h3>
<hr/>
@@ -33,7 +27,7 @@
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
StateHasChanged();
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..b0d5a65
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor
@@ -0,0 +1,138 @@
+@page "/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/Draupnir/ProtectedRoomsEditor"
+@using LibMatrix
+@using LibMatrix.EventTypes.Interop.Draupnir
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
+ if (hs is null) return;
+ data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>(DraupnirProtectedRoomsData.EventId);
+ 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 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..ea39c9a 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/DraupnirProtectedRoomsEditor.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor
@@ -1,7 +1,7 @@
-@page "/Moderation/DraupnirProtectedRoomsEditor"
-@page "/Tools/Moderation/DraupnirProtectedRoomsEditor"
+@page "/Tools/Moderation/Draupnir/ProtectionsEditor"
@using System.Text.Json.Serialization
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using LibMatrix.RoomTypes
<h3>Edit Draupnir protected rooms</h3>
<hr/>
@@ -38,7 +38,7 @@
</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.PowerLevels.UserHasStatePermission(hs.UserId, RoomServerAclEventContent.EventId) ? "X" : "")</td>
<td>@room.Room.RoomId</td>
<td>@room.RoomName</td>
</tr>
@@ -58,7 +58,7 @@
private AuthenticatedHomeserverGeneric hs { get; set; }
protected override async Task OnInitializedAsync() {
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
data = await hs.GetAccountDataAsync<DraupnirProtectedRoomsData>("org.matrix.mjolnir.protected_rooms");
StateHasChanged();
@@ -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..9e70687
--- /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.RoomInfo
+@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 sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
+ 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/FindUsersByRegex.razor b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
new file mode 100644
index 0000000..b62cf57
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor
@@ -0,0 +1,192 @@
+@page "/Tools/Moderation/FindUsersByRegex"
+@using System.Collections.Frozen
+@using ArcaneLibs.Extensions
+@using LibMatrix.RoomTypes
+@using System.Collections.ObjectModel
+@using System.Text.RegularExpressions
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Filters
+@using LibMatrix.Helpers
+<h3>Find users by regex</h3>
+<hr/>
+
+<p>Users (regex): </p>
+<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
+
+<LinkButton OnClick="Execute">Execute</LinkButton>
+<br/>
+<LinkButton OnClick="RemoveKicks">Remove kicks</LinkButton>
+<LinkButton OnClick="RemoveBans">Remove bans</LinkButton>
+<br/>
+
+
+<details>
+ <summary>Results</summary>
+ @foreach (var (userId, events) in matches) {
+ <h4>@userId</h4>
+ <ul>
+ @foreach (var match in events) {
+ <li>
+ <ul>
+ <li>@match.RoomName (<span>@match.Room.RoomId</span>)</li>
+ <li>Membership: @(match.Event.RawContent.ToJson(indent: false)) (sent by @match.Event.Sender)</li>
+ </ul>
+ </li>
+ }
+ </ul>
+ }
+</details>
+
+<br/>
+@foreach (var line in log.Reverse()) {
+ <pre>@line</pre>
+}
+
+@code {
+
+ private ObservableCollection<string> log { get; set; } = new();
+
+ // List<RoomInfo> rooms { get; set; } = new();
+ List<GenericRoom> rooms { get; set; } = [];
+ Dictionary<string, List<Match>> matches = new();
+
+ private string UserIdString {
+ get => string.Join("\n", UserIDs);
+ set => UserIDs = value.Split("\n").Select(x => x.Trim()).Where(x => !string.IsNullOrWhiteSpace(x)).ToList();
+ }
+
+ private List<string> UserIDs { get; set; } = new();
+
+ private AuthenticatedHomeserverGeneric hs { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ log.CollectionChanged += (sender, args) => StateHasChanged();
+ log.Add("Authenticating");
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
+ if (hs is null) return;
+
+ StateHasChanged();
+ Console.WriteLine("Rerendered!");
+ await base.OnInitializedAsync();
+ }
+
+ private async Task<string> Execute() {
+ log.Add("Constructing sync helper...");
+ var sh = new SyncHelper(hs) {
+ Filter = new SyncFilter() {
+ AccountData = new(types: []),
+ Presence = new(types: []),
+ Room = new() {
+ AccountData = new(types: []),
+ Ephemeral = new(types: []),
+ State = new(types: [RoomMemberEventContent.EventId]),
+ Timeline = new(types: []),
+ IncludeLeave = false
+ },
+ }
+ };
+
+ log.Add("Starting sync...");
+ var res = await sh.SyncAsync();
+
+ log.Add("Got sync response, parsing...");
+
+ var roomNames = (await Task.WhenAll((await hs.GetJoinedRooms()).Select(async room => { return (room.RoomId, await room.GetNameOrFallbackAsync()); }).ToList())).ToFrozenDictionary(x => x.Item1, x => x.Item2);
+
+ foreach (var userIdRegex in UserIDs) {
+ var regex = new Regex(userIdRegex, RegexOptions.Compiled);
+ log.Add($"Searching for {regex}:");
+ foreach (var (roomId, joinedRoom) in res.Rooms.Join) {
+ log.Add($"- Checking room {roomId}...");
+ foreach (var evt in joinedRoom.State.Events) {
+ if (evt.StateKey is null) continue;
+ if (evt.Type is not RoomMemberEventContent.EventId) continue;
+
+ if (regex.IsMatch(evt.StateKey)) {
+ log.Add($" - Found match in {roomId} for {evt.StateKey}");
+ if (!matches.ContainsKey(evt.StateKey)) {
+ matches[evt.StateKey] = new();
+ }
+
+ var room = hs.GetRoom(roomId);
+ matches[evt.StateKey].Add(new Match {
+ Room = room,
+ Event = evt,
+ RoomName = roomNames[roomId]
+ });
+ }
+ }
+ }
+ }
+
+ log.Add("Done!");
+
+ StateHasChanged();
+
+ return "";
+ }
+
+ public string? ImportFromRoomId { get; set; }
+
+ private async Task DoImportFromRoomId() {
+ try {
+ if (ImportFromRoomId is null) return;
+ var room = rooms.FirstOrDefault(x => x.RoomId == ImportFromRoomId);
+ UserIdString = string.Join("\n", (await room.GetMembersListAsync()).Select(x => x.StateKey));
+ }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ log.Add("Could not fetch members list!\n" + e.ToString());
+ }
+
+ StateHasChanged();
+ }
+
+ private class Match {
+ public GenericRoom Room;
+ public StateEventResponse Event;
+ public string RoomName { get; set; }
+ }
+
+ private async IAsyncEnumerable<Match> GetMatches(string userId) {
+ var results = rooms.Select(async room => {
+ var state = await room.GetStateEventOrNullAsync(room.RoomId, userId);
+ if (state is not null) {
+ return new Match {
+ Room = room,
+ Event = state,
+ RoomName = await room.GetNameOrFallbackAsync()
+ };
+ }
+
+ return null;
+ }).ToAsyncEnumerable();
+ await foreach (var result in results) {
+ if (result is not null) {
+ yield return result;
+ }
+ }
+ }
+
+ private Task RemoveKicks() {
+ foreach (var (userId, matches) in matches) {
+ matches.RemoveAll(x => x.Event.ContentAs<RoomMemberEventContent>()!.Membership == "leave" && x.Event.Sender != x.Event.StateKey);
+ }
+
+ matches.RemoveAll((x, y) => y.Count == 0);
+ StateHasChanged();
+ return Task.CompletedTask;
+ }
+
+ private Task RemoveBans() {
+ foreach (var (userId, matches) in matches) {
+ matches.RemoveAll(x => x.Event.ContentAs<RoomMemberEventContent>()!.Membership == "ban" && x.Event.Sender != x.Event.StateKey);
+ }
+
+ matches.RemoveAll((x, y) => y.Count == 0);
+ StateHasChanged();
+ return Task.CompletedTask;
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
index 2123d4d..5c5946f 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor
@@ -1,6 +1,8 @@
@page "/Tools/Moderation/InviteCounter"
@using System.Collections.ObjectModel
-@using LibMatrix.EventTypes.Spec.State
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Filters
<h3>Invite counter</h3>
<hr/>
@@ -13,7 +15,7 @@
<details>
<summary>Results</summary>
- @foreach (var (userId, events) in invites.OrderByDescending(x=>x.Value).ToList()) {
+ @foreach (var (userId, events) in invites.OrderByDescending(x => x.Value).ToList()) {
<p>@userId: @events</p>
}
</details>
@@ -27,16 +29,15 @@
private ObservableCollection<string> log { get; set; } = new();
private Dictionary<string, int> invites { get; set; } = new();
private AuthenticatedHomeserverGeneric hs { get; set; }
-
+
[Parameter, SupplyParameterFromQuery(Name = "room")]
public string roomId { get; set; }
-
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
-
+
StateHasChanged();
Console.WriteLine("Rerendered!");
await base.OnInitializedAsync();
@@ -44,22 +45,21 @@
private async Task<string> Execute() {
var room = hs.GetRoom(roomId);
- var events = room.GetManyMessagesAsync(limit: int.MaxValue);
+ var filter = new SyncFilter.EventFilter() { Types = [RoomMemberEventContent.EventId] };
+ var events = room.GetManyMessagesAsync(limit: int.MaxValue, filter: filter.ToJson(ignoreNull: true, indent: false));
await foreach (var resp in events) {
var all = resp.State.Concat(resp.Chunk);
foreach (var evt in all) {
- if(evt.Type != RoomMemberEventContent.EventId) continue;
+ if (evt.Type != RoomMemberEventContent.EventId) continue;
var content = evt.TypedContent as RoomMemberEventContent;
- if(content.Membership != "invite") continue;
- if(!invites.ContainsKey(evt.Sender)) invites[evt.Sender] = 0;
- invites[evt.Sender]++;
+ if (content?.Membership != "invite") continue;
+ invites.TryAdd(evt.Sender!, 0);
+ invites[evt.Sender!]++;
}
log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline");
}
-
-
-
+
StateHasChanged();
return "";
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
index ea1e5f6..8fdad84 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor
@@ -1,6 +1,7 @@
@page "/Tools/Moderation/MassCMEBan"
@using System.Collections.ObjectModel
@using LibMatrix.EventTypes.Spec.State.Policy
+@using LibMatrix.RoomTypes
<h3>User Trace</h3>
<hr/>
@@ -17,19 +18,19 @@
}
@code {
+
// TODO: Properly implement page to be more useful
private ObservableCollection<string> log { get; set; } = new();
private AuthenticatedHomeserverGeneric hs { get; set; }
-
+
[Parameter, SupplyParameterFromQuery(Name = "room")]
public string roomId { get; set; }
-
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
-
+
StateHasChanged();
Console.WriteLine("Rerendered!");
await base.OnInitializedAsync();
@@ -37,33 +38,41 @@
private async Task<string> Execute() {
var room = hs.GetRoom("!fTjMjIzNKEsFlUIiru:neko.dev");
- // var room = hs.GetRoom("!yf7OpOiRDXx6zUGpT6:conduit.rory.gay");
- var users = roomId.Split("\n").Select(x => x.Trim()).Where(x=>x.StartsWith('@')).ToList();
- foreach (var user in users) {
- var exists = false;
- try {
- exists = !string.IsNullOrWhiteSpace((await room.GetStateAsync<UserPolicyRuleEventContent>(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'))).Entity);
- } catch (Exception e) {
- log.Add($"Failed to get {user}");
- }
+ // var room = hs.GetRoom("!IVSjKMsVbjXsmUTuRR:rory.gay");
+ var users = roomId.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList();
+ var tasks = users.Select(x => ExecuteBan(room, x)).ToList();
+ await Task.WhenAll(tasks);
+
+ StateHasChanged();
+
+ return "";
+ }
- if (!exists) {
+ private async Task ExecuteBan(GenericRoom room, string user) {
+ var exists = false;
+ try {
+ exists = !string.IsNullOrWhiteSpace((await room.GetStateAsync<UserPolicyRuleEventContent>(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'))).Entity);
+ }
+ catch (Exception e) {
+ log.Add($"Failed to get {user}");
+ }
+
+ if (!exists) {
+ try {
var evt = await room.SendStateEventAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'), new UserPolicyRuleEventContent() {
Entity = user,
- Reason = "spam (invite)",
+ Reason = "spam",
Recommendation = "m.ban"
});
log.Add($"Sent {evt.EventId} to ban {user}");
}
- else {
- log.Add($"User {user} already exists");
+ catch (Exception e) {
+ log.Add($"Failed to ban {user}: {e}");
}
}
-
-
- StateHasChanged();
-
- return "";
+ else {
+ log.Add($"User {user} already exists");
+ }
}
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
index e5ba004..1ec3cd0 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
@@ -1,31 +1,162 @@
@page "/Tools/Moderation/MembershipHistory"
+@using System.Collections.Frozen
@using System.Collections.ObjectModel
+@using System.Diagnostics
+@using System.Text.Json
+@using ArcaneLibs.Extensions
@using LibMatrix
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+@using LibMatrix.Filters
+@{
+ var sw = Stopwatch.StartNew();
+ Console.WriteLine("Start render");
+}
<h3>Membership history viewer</h3>
<hr/>
-
<br/>
<span>Room ID: </span>
-<InputText @bind-Value="@roomId"></InputText>
+<InputText @bind-Value="@RoomId"></InputText>
<LinkButton OnClick="@Execute">Execute</LinkButton>
-<p><InputCheckbox @bind-Value="ChronologicalOrder"/> Chronological order</p>
+<p>
+ <span><InputCheckbox @bind-Value="ChronologicalOrder"/>Chronological order</span>
+ <span><InputCheckbox @bind-Value="DoDisambiguate"/>Enable extended filters</span>
+</p>
<p>
<span>Show </span>
- <InputCheckbox @bind-Value="ShowJoins"/> joins
- <InputCheckbox @bind-Value="ShowLeaves"/> leaves
- <InputCheckbox @bind-Value="ShowUpdates"/> profile updates
- <InputCheckbox @bind-Value="ShowKnocks"/> knocks
- <InputCheckbox @bind-Value="ShowInvites"/> invites
- <InputCheckbox @bind-Value="ShowKicks"/> kicks
- <InputCheckbox @bind-Value="ShowBans"/> bans
+ <span><InputCheckbox @bind-Value="ShowJoins"/> joins</span>
+ <span><InputCheckbox @bind-Value="ShowLeaves"/> leaves</span>
+ <span><InputCheckbox @bind-Value="ShowKnocks"/> knocks</span>
+ <span><InputCheckbox @bind-Value="ShowInvites"/> invites</span>
+ <span><InputCheckbox @bind-Value="ShowBans"/> bans</span>
</p>
<p>
- <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = false; })">Hide all</LinkButton>
- <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = true; })">Show all</LinkButton>
- <LinkButton OnClick="@(async () => { ShowJoins ^= true; ShowLeaves ^= true; ShowUpdates ^= true; ShowKnocks ^= true; ShowInvites ^= true; ShowKicks ^= true; ShowBans ^= true; })">Toggle all</LinkButton>
+ <LinkButton OnClick="@(async () => {
+ ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false;
+ StateHasChanged();
+ })">Hide all
+ </LinkButton>
+ <LinkButton OnClick="@(async () => {
+ ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true;
+ StateHasChanged();
+ })">Show all
+ </LinkButton>
+ <LinkButton OnClick="@(async () => {
+ ShowJoins ^= true;
+ ShowLeaves ^= true;
+ ShowKnocks ^= true;
+ ShowInvites ^= true;
+ ShowBans ^= true;
+ StateHasChanged();
+ })">Toggle all
+ </LinkButton>
</p>
<p>
+ <span><InputCheckbox @bind-Value="DoDisambiguate"/> Disambiguate </span>
+ @if (DoDisambiguate) {
+ <span><InputCheckbox @bind-Value="DisambiguateKicks"/> kicks</span>
+ <span><InputCheckbox @bind-Value="DisambiguateUnbans"/> unbans</span>
+ <span><InputCheckbox @bind-Value="DisambiguateProfileUpdates"/> profile updates</span>
+ <details style="display: inline-block; vertical-align: top;">
+ <summary>
+ <InputCheckbox @bind-Value="DisambiguateInviteActions"/>
+ invite actions
+ </summary>
+ <span><InputCheckbox @bind-Value="DisambiguateInviteAccepted"/> accepted</span>
+ <span><InputCheckbox @bind-Value="DisambiguateInviteRejected"/> rejected</span>
+ <span><InputCheckbox @bind-Value="DisambiguateInviteRetracted"/> retracted</span>
+ </details>
+ <details style="display: inline-block; vertical-align: top;">
+ <summary>
+ <InputCheckbox @bind-Value="DisambiguateKnockActions"/>
+ knock actions
+ </summary>
+ <span><InputCheckbox @bind-Value="DisambiguateKnockAccepted"/> accepted</span>
+ <span><InputCheckbox @bind-Value="DisambiguateKnockRejected"/> rejected</span>
+ <span><InputCheckbox @bind-Value="DisambiguateKnockRetracted"/> retracted</span>
+ </details>
+ }
+</p>
+@if (DoDisambiguate) {
+ <p>
+ <span>Show </span>
+ @if (DisambiguateKicks) {
+ <span><InputCheckbox @bind-Value="ShowKicks"/> kicks</span>
+ }
+ @if (DisambiguateUnbans) {
+ <span><InputCheckbox @bind-Value="ShowUnbans"/> unbans</span>
+ }
+ @if (DisambiguateProfileUpdates) {
+ <span><InputCheckbox @bind-Value="ShowProfileUpdates"/> profile updates</span>
+ }
+ @if (DisambiguateInviteActions) {
+ <details style="display: inline-block; vertical-align: top;">
+ <summary>
+ <InputCheckbox @bind-Value="ShowInviteActions"/>
+ invite actions
+ </summary>
+ @if (DisambiguateInviteAccepted) {
+ <span><InputCheckbox @bind-Value="ShowInviteAccepted"/> accepted</span>
+ }
+
+ @if (DisambiguateInviteRejected) {
+ <span><InputCheckbox @bind-Value="ShowInviteRejected"/> rejected</span>
+ }
+
+ @if (DisambiguateInviteRetracted) {
+ <span><InputCheckbox @bind-Value="ShowInviteRetracted"/> retracted</span>
+ }
+ </details>
+ }
+ @if (DisambiguateKnockActions) {
+ <details style="display: inline-block; vertical-align: top;">
+ <summary>
+ <InputCheckbox @bind-Value="ShowKnockActions"/>
+ knock actions
+ </summary>
+ @if (DisambiguateKnockAccepted) {
+ <span><InputCheckbox @bind-Value="ShowKnockAccepted"/> accepted</span>
+ }
+
+ @if (DisambiguateKnockRejected) {
+ <span><InputCheckbox @bind-Value="ShowKnockRejected"/> rejected</span>
+ }
+
+ @if (DisambiguateKnockRetracted) {
+ <span><InputCheckbox @bind-Value="ShowKnockRetracted"/> retracted</span>
+ }
+ </details>
+ }
+ </p>
+
+ <p>
+ <LinkButton OnClick="@(async () => {
+ DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false;
+ StateHasChanged();
+ })">Un-disambiguate all
+ </LinkButton>
+ <LinkButton OnClick="@(async () => {
+ DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true;
+ StateHasChanged();
+ })">Disambiguate all
+ </LinkButton>
+ <LinkButton OnClick="@(async () => {
+ DisambiguateProfileUpdates ^= true;
+ DisambiguateKicks ^= true;
+ DisambiguateUnbans ^= true;
+ DisambiguateInviteAccepted ^= true;
+ DisambiguateInviteRejected ^= true;
+ DisambiguateInviteRetracted ^= true;
+ DisambiguateKnockAccepted ^= true;
+ DisambiguateKnockRejected ^= true;
+ DisambiguateKnockRetracted ^= true;
+ DisambiguateKnockActions ^= true;
+ DisambiguateInviteActions ^= true;
+ StateHasChanged();
+ })">Toggle all
+ </LinkButton>
+ </p>
+}
+<p>
<span>Sender: </span>
<InputSelect @bind-Value="Sender">
<option value="">All</option>
@@ -44,92 +175,121 @@
</InputSelect>
</p>
-
+@{ Console.WriteLine($"Rendering took {sw.Elapsed} for {Memberships.Count} items"); }
<br/>
-<details>
+<details open>
<summary>Results</summary>
@{
- Dictionary<string, StateEventResponse> previousMemberships = [];
- var filteredMemberships = Memberships.AsEnumerable();
- if (ChronologicalOrder) {
- filteredMemberships = filteredMemberships.Reverse();
- }
- if(!string.IsNullOrWhiteSpace(Sender)) {
- filteredMemberships = filteredMemberships.Where(x => x.Sender == Sender);
- }
- if(!string.IsNullOrWhiteSpace(User)) {
- filteredMemberships = filteredMemberships.Where(x => x.StateKey == User);
- }
-
- @foreach (var membership in filteredMemberships) {
- RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent;
- @switch (content.Membership) {
- case RoomMemberEventContent.MembershipTypes.Invite: {
- if (_showInvites) {
- <p style="color: green;">@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
- }
-
- break;
- }
- case RoomMemberEventContent.MembershipTypes.Ban: {
- if (_showBans) {
- <p style="color: red;">@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
- }
-
- break;
- }
- case RoomMemberEventContent.MembershipTypes.Leave: {
- if (membership.Sender == membership.StateKey) {
- if (_showLeaves) {
- <p style="color: #C66;">@membership.Sender left the room</p>
- }
- }
- else {
- if (_showKicks) {
- <p style="color: darkorange;">@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
- }
- }
-
- break;
- }
- case RoomMemberEventContent.MembershipTypes.Knock: {
- if (_showKnocks) {
- <p>@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
- }
-
- break;
- }
- case RoomMemberEventContent.MembershipTypes.Join: {
- if (previousMemberships.TryGetValue(membership.StateKey, out var previous)
- && (previous.TypedContent as RoomMemberEventContent).Membership == RoomMemberEventContent.MembershipTypes.Join) {
- if (_showUpdates) {
- <p style="color: #777;">@membership.Sender changed their profile</p>
- }
- }
- else {
- if (_showJoins) {
- <p style="color: #6C6;">@membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
- }
+ var filteredMemberships = GetFilteredMemberships();
+ }
+ <table>
+ @foreach (var membershipEntry in filteredMemberships) {
+ var (transition, membership, previousMembership) = membershipEntry;
+ RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent ?? throw new InvalidOperationException("Event is not a RoomMemberEventContent!");
+ RoomMemberEventContent? previousContent = previousMembership?.TypedContent as RoomMemberEventContent;
+
+ <tr>
+ <td>@DateTimeOffset.FromUnixTimeMilliseconds(membership.OriginServerTs ?? 0).ToString("g")</td>
+ <td>
+ @switch (transition) {
+ case MembershipTransition.None:
+ <b>Unknown membership! Got None</b>
+ break;
+ case MembershipTransition.Join:
+ <p style="color: #6C6;">
+ @membership.StateKey joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")<br/>
+ Display name: @content.DisplayName<br/>
+ Avatar URL: @content.AvatarUrl
+ </p>
+ break;
+ case MembershipTransition.Leave:
+ <p style="color: #C66;">
+ @membership.StateKey left the room
+ </p>
+ break;
+ case MembershipTransition.Knock:
+ <p style="color: #426">
+ @membership.StateKey knocked @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.Invite:
+ <p style="color: #262;">
+ @membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.Ban:
+ <p style="color: red;">
+ @membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ @* disambiguated *@
+ case MembershipTransition.Kick:
+ <p style="color: darkorange;">
+ @membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.ProfileUpdate:
+ <p style="color: #777;">
+ @membership.Sender changed their profile<br/>
+ Display name: @previousContent!.DisplayName -> @content.DisplayName<br/>
+ Avatar URL: @previousContent.AvatarUrl -> @content.AvatarUrl
+ </p>
+ break;
+ case MembershipTransition.InviteAccepted:
+ <p style="color: #084;">
+ @membership.StateKey accepted the invite
+ from @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(invite reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(accept reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.KnockAccepted:
+ <p style="color: #288;">
+ @membership.StateKey's knock was accepted
+ by @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(knock reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(accept reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.KnockRejected:
+ <p style="color: #828;">
+ @membership.StateKey's knock was rejected
+ by @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(knock reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reject reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.Unban:
+ <p style="color: #0C0;">
+ @membership.Sender unbanned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.InviteRejected:
+ <p style="color: #733;">
+ @membership.StateKey rejected the invite
+ from @previousMembership!.Sender @(string.IsNullOrWhiteSpace(previousContent?.Reason) ? "" : $"(invite reason: {previousContent.Reason})") @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reject reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.InviteRetracted:
+ <p style="color: #844;">
+ @membership.Sender retracted the invite
+ for @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ case MembershipTransition.KnockRetracted:
+ <p style="color: #b55;">
+ @membership.Sender retracted the knock
+ for @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ </p>
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
}
-
- break;
- }
- default: {
- <b>Unknown membership @content.Membership!</b>
- break;
- }
- }
-
- previousMemberships[membership.StateKey] = membership;
+ </td>
+ </tr>
}
- }
+ </table>
</details>
<br/>
<details open>
<summary>Log</summary>
- @foreach (var line in log.Reverse()) {
+ @foreach (var line in Log.Reverse()) {
<pre>@line</pre>
}
</details>
@@ -138,139 +298,289 @@
#region Filter bindings
- private bool _chronologicalOrder = false;
-
- private bool ChronologicalOrder {
- get => _chronologicalOrder;
- set {
- _chronologicalOrder = value;
- StateHasChanged();
- }
- }
+ private bool ChronologicalOrder { get; set; }
+ private bool ShowJoins { get; set; } = true;
+ private bool ShowLeaves { get; set; } = true;
+ private bool ShowKnocks { get; set; } = true;
+ private bool ShowInvites { get; set; } = true;
+ private bool ShowBans { get; set; } = true;
+
+ private bool DoDisambiguate { get; set; } = true;
+ private bool DisambiguateProfileUpdates { get => field && DoDisambiguate; set; } = true;
+ private bool DisambiguateKicks { get => field && DoDisambiguate; set; } = true;
+ private bool DisambiguateUnbans { get => field && DoDisambiguate; set; } = true;
+ private bool DisambiguateInviteAccepted { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true;
+ private bool DisambiguateInviteRejected { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true;
+ private bool DisambiguateInviteRetracted { get => field && DoDisambiguate && DisambiguateInviteActions; set; } = true;
+ private bool DisambiguateKnockAccepted { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true;
+ private bool DisambiguateKnockRejected { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true;
+ private bool DisambiguateKnockRetracted { get => field && DoDisambiguate && DisambiguateKnockActions; set; } = true;
+
+ private bool DisambiguateKnockActions { get => field && DoDisambiguate; set; } = true;
+ private bool DisambiguateInviteActions { get => field && DoDisambiguate; set; } = true;
- private bool _showJoins = true;
+ private bool ShowProfileUpdates {
+ get => field && DisambiguateProfileUpdates;
+ set;
+ } = true;
- private bool ShowJoins {
- get => _showJoins;
+ private bool ShowKicks {
+ get => field && DisambiguateKicks;
+ set;
+ } = true;
+
+ private bool ShowUnbans {
+ get => field && DisambiguateUnbans;
+ set;
+ } = true;
+
+ private bool ShowInviteAccepted {
+ get => field && DisambiguateInviteAccepted;
+ set;
+ } = true;
+
+ private bool ShowInviteRejected {
+ get => field && DisambiguateInviteRejected;
+ set;
+ } = true;
+
+ private bool ShowInviteRetracted {
+ get => field && DisambiguateInviteRetracted;
+ set;
+ } = true;
+
+ private bool ShowKnockAccepted {
+ get => field && DisambiguateKnockAccepted;
+ set;
+ } = true;
+
+ private bool ShowKnockRejected {
+ get => field && DisambiguateKnockRejected;
+ set;
+ } = true;
+
+ private bool ShowKnockRetracted {
+ get => field && DisambiguateKnockRetracted;
+ set;
+ } = true;
+
+ private bool ShowKnockActions {
+ get => field && DisambiguateKnockActions;
+ set;
+ } = true;
+
+ private bool ShowInviteActions {
+ get => field && DisambiguateInviteActions;
+ set;
+ } = true;
+
+ [Parameter, SupplyParameterFromQuery(Name = "sender")]
+ public string Sender { get; set; } = "";
+
+ [Parameter, SupplyParameterFromQuery(Name = "user")]
+ public string User { get; set; } = "";
+
+ [Parameter, SupplyParameterFromQuery(Name = "filter")]
+ public string Filter {
+ get;
set {
- _showJoins = value;
+ field = value;
+ if (string.IsNullOrWhiteSpace(value)) return;
+ var parts = value.Split(',');
+ ShowJoins = parts.Contains("join");
+ ShowLeaves = parts.Contains("leave");
+ ShowKnocks = parts.Contains("knock");
+ ShowInvites = parts.Contains("invite");
+ ShowBans = parts.Contains("ban");
StateHasChanged();
}
- }
-
- private bool _showLeaves = true;
+ } = "";
- private bool ShowLeaves {
- get => _showLeaves;
- set {
- _showLeaves = value;
- StateHasChanged();
- }
- }
+#endregion
- private bool _showUpdates = true;
+ private ObservableCollection<string> Log { get; set; } = new();
+ private List<StateEventResponse> Memberships { get; set; } = [];
+ private AuthenticatedHomeserverGeneric Homeserver { get; set; }
- private bool ShowUpdates {
- get => _showUpdates;
- set {
- _showUpdates = value;
- StateHasChanged();
- }
- }
+ [Parameter, SupplyParameterFromQuery(Name = "room")]
+ public string RoomId { get; set; } = "";
- private bool _showKnocks = true;
+ protected override async Task OnInitializedAsync() {
+ Log.CollectionChanged += (sender, args) => StateHasChanged();
+ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
+ if (Homeserver is null) return;
- private bool ShowKnocks {
- get => _showKnocks;
- set {
- _showKnocks = value;
- StateHasChanged();
- }
+ StateHasChanged();
+ Console.WriteLine("Rerendered!");
+ await base.OnInitializedAsync();
+ if (!string.IsNullOrWhiteSpace(RoomId))
+ await Execute();
}
- private bool _showInvites = true;
+ private async Task Execute() {
+ Memberships.Clear();
+ var room = Homeserver.GetRoom(RoomId);
+ var filter = new SyncFilter.EventFilter() { Types = [RoomMemberEventContent.EventId] };
+ var events = room.GetManyMessagesAsync(limit: int.MaxValue, filter: filter.ToJson(ignoreNull: true, indent: false));
+ await foreach (var resp in events) {
+ var all = resp.State.Concat(resp.Chunk)
+ // ugly hack, because some users fuck around too much
+ .Select(x => {
+ if (x.RawContent?["displayname"]?.GetValueKind() != JsonValueKind.String)
+ x.RawContent?.Remove("displayname");
+ if (x.RawContent?["avatar_url"]?.GetValueKind() is not JsonValueKind.String)
+ x.RawContent?.Remove("avatar_url");
+ return x;
+ });
+ Memberships.AddRange(all.Where(x => x.Type == RoomMemberEventContent.EventId));
- private bool ShowInvites {
- get => _showInvites;
- set {
- _showInvites = value;
- StateHasChanged();
+ Log.Add($"Got {resp.State.Count} state and {resp.Chunk.Count} timeline events.");
}
- }
- private bool _showKicks = true;
+ Log.Add("Reached end of timeline!");
- private bool ShowKicks {
- get => _showKicks;
- set {
- _showKicks = value;
- StateHasChanged();
- }
+ StateHasChanged();
}
- private bool _showBans = true;
+ private readonly struct MembershipEntry {
+ public required MembershipTransition State { get; init; }
+ public required StateEventResponse Event { get; init; }
+ public required StateEventResponse? Previous { get; init; }
- private bool ShowBans {
- get => _showBans;
- set {
- _showBans = value;
- StateHasChanged();
+ public void Deconstruct(out MembershipTransition transition, out StateEventResponse evt, out StateEventResponse? prev) {
+ transition = State;
+ evt = Event;
+ prev = Previous;
}
}
-
- private string sender = "";
-
- private string Sender {
- get => sender;
- set {
- sender = value;
- StateHasChanged();
- }
+
+ private enum MembershipTransition : byte {
+ None,
+ Join,
+ Leave,
+ Knock,
+ Invite,
+ Ban,
+
+ // disambiguated
+ ProfileUpdate,
+ Kick,
+ Unban,
+ InviteAccepted,
+ InviteRejected,
+ InviteRetracted,
+ KnockAccepted,
+ KnockRejected,
+ KnockRetracted
}
-
- private string user = "";
-
- private string User {
- get => user;
- set {
- user = value;
- StateHasChanged();
+
+ private static IEnumerable<MembershipEntry> GetTransitions(List<StateEventResponse> evts) {
+ Dictionary<string, MembershipEntry> transitions = new();
+ foreach (var evt in evts.OrderBy(x => x.OriginServerTs)) {
+ var content = evt.TypedContent as RoomMemberEventContent ?? throw new InvalidOperationException("Event is not a RoomMemberEventContent!");
+ var prev = transitions.GetValueOrDefault(evt.StateKey!) as MembershipEntry?;
+ transitions[evt.StateKey ?? throw new Exception("Member event has no state key??")] = new MembershipEntry {
+ Event = evt,
+ Previous = prev?.Event,
+ State = content.Membership switch {
+ RoomMemberEventContent.MembershipTypes.Join =>
+ prev?.State switch {
+ MembershipTransition.Join or MembershipTransition.InviteAccepted => MembershipTransition.ProfileUpdate,
+ MembershipTransition.Invite => MembershipTransition.InviteAccepted,
+ _ => MembershipTransition.Join
+ },
+ RoomMemberEventContent.MembershipTypes.Leave =>
+ evt.Sender == evt.StateKey
+ ? prev?.State switch {
+ MembershipTransition.Knock => MembershipTransition.KnockRetracted,
+ MembershipTransition.Invite => MembershipTransition.InviteRejected,
+ _ => MembershipTransition.Leave
+ }
+ : prev?.State switch {
+ // not self
+ MembershipTransition.Knock => MembershipTransition.KnockRejected,
+ MembershipTransition.Invite => MembershipTransition.InviteRetracted,
+ _ => MembershipTransition.Kick,
+ },
+ RoomMemberEventContent.MembershipTypes.Invite =>
+ prev?.State switch {
+ MembershipTransition.Knock => MembershipTransition.KnockAccepted,
+ _ => MembershipTransition.Invite
+ },
+ RoomMemberEventContent.MembershipTypes.Knock => MembershipTransition.Knock,
+ RoomMemberEventContent.MembershipTypes.Ban => MembershipTransition.Ban,
+ _ => MembershipTransition.None
+ }
+ };
+ yield return transitions[evt.StateKey];
}
}
-#endregion
-
- private ObservableCollection<string> log { get; set; } = new();
- private List<StateEventResponse> Memberships { get; set; } = [];
- private AuthenticatedHomeserverGeneric hs { get; set; }
-
- [Parameter, SupplyParameterFromQuery(Name = "room")]
- public string roomId { get; set; }
-
- protected override async Task OnInitializedAsync() {
- log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
- if (hs is null) return;
+ private IEnumerable<MembershipEntry> Disambiguated(IEnumerable<MembershipEntry> entries) {
+ FrozenDictionary<MembershipTransition, MembershipTransition> disambiguated = new Dictionary<MembershipTransition, MembershipTransition>() {
+ { MembershipTransition.ProfileUpdate, MembershipTransition.Join },
+ { MembershipTransition.Kick, MembershipTransition.Leave },
+ { MembershipTransition.Unban, MembershipTransition.Leave },
+ { MembershipTransition.InviteAccepted, MembershipTransition.Join },
+ { MembershipTransition.InviteRejected, MembershipTransition.Leave },
+ { MembershipTransition.InviteRetracted, MembershipTransition.Leave },
+ { MembershipTransition.KnockAccepted, MembershipTransition.Invite },
+ { MembershipTransition.KnockRejected, MembershipTransition.Leave },
+ { MembershipTransition.KnockRetracted, MembershipTransition.Leave }
+ }.ToFrozenDictionary();
+
+ foreach (var entry in entries) {
+ if (!DoDisambiguate) {
+ yield return entry;
+ continue;
+ }
- StateHasChanged();
- Console.WriteLine("Rerendered!");
- await base.OnInitializedAsync();
- if (!string.IsNullOrWhiteSpace(roomId))
- await Execute();
+ var newState = entry.State switch {
+ MembershipTransition.ProfileUpdate when !DoDisambiguate || !DisambiguateProfileUpdates => MembershipTransition.Join,
+ MembershipTransition.Kick when !DoDisambiguate || !DisambiguateKicks => MembershipTransition.Leave,
+ MembershipTransition.Unban when !DoDisambiguate || !DisambiguateUnbans => MembershipTransition.Leave,
+ MembershipTransition.InviteAccepted when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteAccepted => MembershipTransition.Join,
+ MembershipTransition.InviteRejected when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRejected => MembershipTransition.Leave,
+ MembershipTransition.InviteRetracted when !DoDisambiguate || !DisambiguateInviteActions || !DisambiguateInviteRetracted => MembershipTransition.Leave,
+ MembershipTransition.KnockAccepted when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockAccepted => MembershipTransition.Invite,
+ MembershipTransition.KnockRejected when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRejected => MembershipTransition.Leave,
+ MembershipTransition.KnockRetracted when !DoDisambiguate || !DisambiguateKnockActions || !DisambiguateKnockRetracted => MembershipTransition.Leave,
+ _ => entry.State
+ };
+ if (newState != entry.State) {
+ yield return entry with { State = newState };
+ }
+ else yield return entry;
+ }
}
- private async Task Execute() {
- Memberships.Clear();
- var room = hs.GetRoom(roomId);
- var events = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000);
- await foreach (var resp in events) {
- var all = resp.State.Concat(resp.Chunk);
- Memberships.AddRange(all.Where(x => x.Type == RoomMemberEventContent.EventId));
-
- log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline");
+ private IEnumerable<MembershipEntry> GetFilteredMemberships() {
+ var filteredMemberships = GetTransitions(Memberships);
+ if (!string.IsNullOrWhiteSpace(Sender)) filteredMemberships = filteredMemberships.Where(x => x.Event.Sender == Sender);
+ if (!string.IsNullOrWhiteSpace(User)) filteredMemberships = filteredMemberships.Where(x => x.Event.StateKey == User);
+ filteredMemberships = Disambiguated(filteredMemberships);
+
+ if (!ShowJoins) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Join);
+ if (!ShowLeaves) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Leave);
+ if (!ShowKnocks) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Knock);
+ if (!ShowInvites) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Invite);
+ if (!ShowBans) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Ban);
+ // extended filters
+ if (DoDisambiguate) {
+ if (!DisambiguateProfileUpdates || !ShowProfileUpdates) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.ProfileUpdate);
+ if (!DisambiguateKicks || !ShowKicks) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Kick);
+ if (!DisambiguateUnbans || !ShowUnbans) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.Unban);
+ if (!DisambiguateInviteActions || !ShowInviteActions || !DisambiguateInviteAccepted || !ShowInviteAccepted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.InviteAccepted);
+ if (!DisambiguateInviteActions || !ShowInviteActions || !DisambiguateInviteRejected || !ShowInviteRejected) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.InviteRejected);
+ if (!DisambiguateInviteActions || !ShowInviteActions || !DisambiguateInviteRetracted || !ShowInviteRetracted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.InviteRetracted);
+ if (!DisambiguateKnockActions || !ShowKnockActions || !DisambiguateKnockAccepted || !ShowKnockAccepted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.KnockAccepted);
+ if (!DisambiguateKnockActions || !ShowKnockActions || !DisambiguateKnockRejected || !ShowKnockRejected) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.KnockRejected);
+ if (!DisambiguateKnockActions || !ShowKnockActions || !DisambiguateKnockRetracted || !ShowKnockRetracted) filteredMemberships = filteredMemberships.Where(x => x.State != MembershipTransition.KnockRetracted);
}
- StateHasChanged();
+ if (!ChronologicalOrder) filteredMemberships = filteredMemberships.Reverse();
+
+ return filteredMemberships;
}
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
index b8baeb8..736e59a 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor
@@ -2,7 +2,7 @@
@using LibMatrix.RoomTypes
@using System.Collections.ObjectModel
@using LibMatrix
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
<h3>Room intersections</h3>
<hr/>
@@ -113,7 +113,7 @@
protected override async Task OnInitializedAsync() {
Log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
StateHasChanged();
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
index 915f8dc..c3cc09c 100644
--- a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor
@@ -3,13 +3,15 @@
@using LibMatrix.RoomTypes
@using System.Collections.ObjectModel
@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
<h3>User Trace</h3>
<hr/>
<p>Users: </p>
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
<br/>
-<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton>
+<InputText @bind-Value="@ImportFromRoomId"></InputText>
+<LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton>
<details>
<summary>Rooms to be searched (@rooms.Count)</summary>
@@ -24,18 +26,21 @@
<details>
<summary>Results</summary>
- @foreach (var (userId, events) in matches) {
+ @foreach (var (userId, events) in matches.OrderBy(x=>x.Key)) {
<h4>@userId</h4>
- <ul>
- @foreach (var match in events) {
- <li>
- <ul>
- <li>@match.RoomName (<span>@match.Room.RoomId</span>)</li>
- <li>Membership: @(match.Event.RawContent.ToJson(indent: false))</li>
- </ul>
- </li>
+ <table>
+ @foreach (var match in events.OrderBy(x=>x.RoomName)) {
+ <tr>
+ <td>@match.RoomName (<span>@match.Room.RoomId</span>)</td>
+ <td>
+ <details>
+ <summary>@SummarizeMembership(match.Event)</summary>
+ <pre>@match.Event.RawContent.ToJson(indent: true)</pre>
+ </details>
+ </td>
+ </tr>
}
- </ul>
+ </table>
}
</details>
@@ -61,56 +66,34 @@
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
- // var sessions = await RMUStorage.GetAllTokens();
- // var baseRooms = new List<GenericRoom>();
- // foreach (var userAuth in sessions) {
- // var session = await RMUStorage.GetSession(userAuth);
- // if (session is not null) {
- // baseRooms.AddRange(await session.GetJoinedRooms());
- // var sessionRooms = (await session.GetJoinedRooms()).Where(x => !rooms.Any(y => y.Room.RoomId == x.RoomId)).ToList();
- // StateHasChanged();
- // log.Add($"Got {sessionRooms.Count} rooms for {userAuth.UserId}");
- // }
- // }
- //
- // log.Add("Done fetching rooms!");
- //
- // baseRooms = baseRooms.DistinctBy(x => x.RoomId).ToList();
- //
- // // rooms.CollectionChanged += (sender, args) => StateHasChanged();
- // var tasks = baseRooms.Select(async newRoom => {
- // bool success = false;
- // while (!success)
- // try {
- // var state = await newRoom.GetFullStateAsListAsync();
- // var newRoomInfo = new RoomInfo(newRoom, state);
- // rooms.Add(newRoomInfo);
- // log.Add($"Got {newRoomInfo.StateEvents.Count} events for {newRoomInfo.RoomName}");
- // success = true;
- // }
- // catch (MatrixException e) {
- // log.Add($"Failed to fetch room {newRoom.RoomId}! {e}");
- // throw;
- // }
- // catch (HttpRequestException e) {
- // log.Add($"Failed to fetch room {newRoom.RoomId}! {e}");
- // }
- // });
- // await Task.WhenAll(tasks);
- //
- // log.Add($"Done fetching members!");
- //
- // UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x));
-
- foreach (var session in await RMUStorage.GetAllTokens()) {
- var _hs = await RMUStorage.GetSession(session);
- if (_hs is not null) {
- rooms.AddRange(await _hs.GetJoinedRooms());
- log.Add($"Got {rooms.Count} rooms after adding {_hs.UserId}");
+
+ var sessions = await sessionStore.GetAllSessions();
+ var tasks = sessions.Select(async session => {
+ try {
+ var _hs = await sessionStore.GetHomeserver(session.Key);
+ if (_hs is not null) {
+ try {
+ var _rooms = await _hs.GetJoinedRooms();
+ if (!_rooms.Any()) return;
+ // Check if homeserver supports `?format=event`:
+ await _rooms.First().GetStateEventAsync(RoomMemberEventContent.EventId, session.Value.Auth.UserId);
+ rooms.AddRange(_rooms);
+ log.Add($"Got {_rooms.Count} rooms for {_hs.UserId}, total {rooms.Count}");
+ }
+ catch (Exception e) {
+ if (e is LibMatrixException { ErrorCode: LibMatrixException.ErrorCodes.M_UNSUPPORTED })
+ log.Add($"Homeserver {_hs.UserId} does not support `?format=event`! Skipping...");
+ else log.Add($"Failed to fetch rooms for {_hs.UserId}! {e}");
+ }
+ }
}
- }
+ catch (Exception e) {
+ log.Add($"Failed to fetch rooms for {session.Value.Auth.UserId}! {e}");
+ }
+ });
+ await Task.WhenAll(tasks);
//get distinct rooms evenly distributed per session, accounting for count per session
rooms = rooms.OrderBy(x => rooms.Count(y => y.Homeserver == x.Homeserver)).DistinctBy(x => x.RoomId).ToList();
@@ -125,17 +108,6 @@
foreach (var userId in UserIDs) {
matches.Add(userId, new List<Match>());
- // foreach (var room in rooms) {
- // var state = room.StateEvents.Where(x => x!.Type == RoomMemberEventContent.EventId).ToList();
- // if (state!.Any(x => x.StateKey == userId)) {
- // matches[userId].Add(new() {
- // Event = state.First(x => x.StateKey == userId),
- // Room = room.Room,
- // RoomName = room.RoomName ?? "No name"
- // });
- // }
- // }
-
log.Add($"Searching for {userId}...");
await foreach (var match in GetMatches(userId)) {
matches[userId].Add(match);
@@ -173,13 +145,19 @@
private async IAsyncEnumerable<Match> GetMatches(string userId) {
var results = rooms.Select(async room => {
- var state = await room.GetStateEventOrNullAsync(room.RoomId, userId);
- if (state is not null) {
- return new Match {
- Room = room,
- Event = state,
- RoomName = await room.GetNameOrFallbackAsync()
- };
+ try {
+ var state = await room.GetStateEventOrNullAsync(RoomMemberEventContent.EventId, userId);
+ if (state is not null) {
+ log.Add($"Found {userId} in {room.RoomId} with membership {state.RawContent.ToJson(indent: false)}");
+ return new Match {
+ Room = room,
+ Event = state,
+ RoomName = await room.GetNameOrFallbackAsync()
+ };
+ }
+ }
+ catch (Exception e) {
+ log.Add($"Failed to fetch state for {userId} in {room.RoomId}! {e}");
}
return null;
@@ -191,4 +169,27 @@
}
}
+ public string SummarizeMembership(StateEventResponse state) {
+ var membership = state.ContentAs<RoomMemberEventContent>();
+ var time = DateTimeOffset.FromUnixTimeMilliseconds(state.OriginServerTs!.Value);
+ return membership switch {
+ { Membership: "invite", Reason: null } => $"Invited by {state.Sender} at {time}",
+ { Membership: "invite", Reason: not null } => $"Invited by {state.Sender} at {time} for {membership.Reason}",
+ { Membership: "join", Reason: null } => $"Joined at {time}",
+ { Membership: "join", Reason: not null } => $"Joined at {time} for {membership.Reason}",
+ { Membership: "leave", Reason: null } => state.Sender == state.StateKey ? $"Left at {time}" : $"Kicked by {state.Sender} at {time}",
+ { Membership: "leave", Reason: not null } => state.Sender == state.StateKey ? $"Left at {time} with reason {membership.Reason}" : $"Kicked by {state.Sender} at {time} for {membership.Reason}",
+ { Membership: "ban", Reason: null } => $"Banned by {state.Sender} at {time}",
+ { Membership: "ban", Reason: not null } => $"Banned by {state.Sender} at {time} for {membership.Reason}",
+ { Membership: "knock", Reason: null } => $"Knocked at {time}",
+ { Membership: "knock", Reason: not null } => $"Knocked at {time} for {membership.Reason}",
+ _ => $"Unknown membership {membership.Membership}, sent at {time} by {state.Sender} for {membership.Reason}"
+ };
+ }
+
+ private async Task ExportJson() {
+ var json = matches.ToJson();
+
+ }
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor
index 80a03f2..ac3c651 100644
--- a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor
+++ b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor
@@ -1,6 +1,6 @@
@page "/Tools/Room/SpaceRestrictedJoins"
@using System.Collections.ObjectModel
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
<h3>Allow space to restricted join children</h3>
<hr/>
@@ -31,7 +31,7 @@
protected override async Task OnInitializedAsync() {
log.CollectionChanged += (sender, args) => StateHasChanged();
- hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
StateHasChanged();
diff --git a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
index 667b518..e5ffd5b 100644
--- a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor
@@ -1,7 +1,7 @@
@page "/Tools/CopyPowerlevel"
@using ArcaneLibs.Extensions
@using LibMatrix
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using LibMatrix.RoomTypes
<h3>Copy powerlevel</h3>
<hr/>
@@ -23,11 +23,11 @@
List<AuthenticatedHomeserverGeneric> hss { get; set; } = new();
protected override async Task OnInitializedAsync() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
- var sessions = await RMUStorage.GetAllTokens();
- foreach (var userAuth in sessions) {
- var session = await RMUStorage.GetSession(userAuth);
+ var sessions = await sessionStore.GetAllSessions();
+ foreach (var userAuth in sessions.Keys) {
+ var session = await sessionStore.GetHomeserver(userAuth);
if (session is not null) {
hss.Add(session);
StateHasChanged();
@@ -42,7 +42,7 @@
private async Task Execute() {
foreach (var hs in hss) {
var rooms = await hs.GetJoinedRooms();
- var tasks = rooms.Select(x=>Execute(hs, x)).ToAsyncEnumerable();
+ var tasks = rooms.Select(x=>ApplyPowerlevelsInRoom(hs, x)).ToAsyncEnumerable();
await foreach (var a in tasks) {
if (!string.IsNullOrWhiteSpace(a)) {
log.Add(a);
@@ -52,7 +52,7 @@
}
}
- private async Task<string> Execute(AuthenticatedHomeserverGeneric hs, GenericRoom room) {
+ private async Task<string> ApplyPowerlevelsInRoom(AuthenticatedHomeserverGeneric hs, GenericRoom room) {
try {
var pls = await room.GetPowerLevelsAsync();
// if (pls.GetUserPowerLevel(hs.WhoAmI.UserId) == pls.UsersDefault) return "I am default PL in " + room.RoomId;
diff --git a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
index a2ad388..c373a37 100644
--- a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
+++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor
@@ -1,7 +1,7 @@
@page "/Tools/MassRoomJoin"
@using ArcaneLibs.Extensions
@using LibMatrix
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
<h3>Mass join room</h3>
<hr/>
<p>Room: </p>
@@ -25,11 +25,11 @@
string roomId { get; set; }
protected override async Task OnInitializedAsync() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
- var sessions = await RMUStorage.GetAllTokens();
- foreach (var userAuth in sessions) {
- var session = await RMUStorage.GetSession(userAuth);
+ var sessions = await sessionStore.GetAllSessions();
+ foreach (var userAuth in sessions.Keys) {
+ var session = await sessionStore.GetHomeserver(userAuth);
if (session is not null) {
hss.Add(session);
StateHasChanged();
diff --git a/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor b/MatrixUtils.Web/Pages/Tools/User/ViewAccountData.razor
index d8b02bb..a393d2e 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>
@@ -16,7 +16,7 @@
Dictionary<string, EventList?> perRoomAccountData = new();
protected override async Task OnInitializedAsync() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
perRoomAccountData = await hs.EnumerateAccountDataPerRoom();
globalAccountData = await hs.EnumerateAccountData();
|