diff --git a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor
index 94c51b2..27fe35e 100644
--- a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor
+++ b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor
@@ -3,6 +3,7 @@
@using ArcaneLibs.Extensions
@using LibMatrix.Extensions
@using LibMatrix.Homeservers
+@using MatrixRoomUtils.Abstractions
@inject ILocalStorageService LocalStorage
@inject NavigationManager NavigationManager
<h3>Debug Tools</h3>
diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor
index 2d1d6c0..ebb0ebb 100644
--- a/MatrixRoomUtils.Web/Pages/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Index.razor
@@ -16,30 +16,29 @@ Small collection of tools to do not-so-everyday things.
<hr/>
<form>
<table>
- @foreach (var __auth in _auth.OrderByDescending(x => x.UserInfo.RoomCount)) {
- var _auth = __auth.UserAuth;
+ @foreach (var session in _sessions.OrderByDescending(x => x.UserInfo.RoomCount)) {
+ var _auth = session.UserAuth;
<tr class="user-entry">
<td>
- <img class="avatar" src="@__auth.UserInfo.AvatarUrl"/>
+ <img class="avatar" src="@session.UserInfo.AvatarUrl"/>
</td>
<td class="user-info">
- @* <div class="user-info"> *@
<p>
<input type="radio" name="csa" checked="@(_currentSession.AccessToken == _auth.AccessToken)" @onclick="@(() => SwitchSession(_auth))" style="text-decoration-line: unset;"/>
- <b>@__auth.UserInfo.DisplayName</b> on <b>@_auth.Homeserver</b><br/>
+ <b>@session.UserInfo.DisplayName</b> on <b>@_auth.Homeserver</b><br/>
</p>
- <span style="display: inline-block; width: 128px;">@__auth.UserInfo.RoomCount rooms</span>
- <a style="color: #888888" href="@("/ServerInfo/"+__auth.Homeserver.ServerName+"/")">@__auth.ServerVersion.Server.Name @__auth.ServerVersion.Server.Version</a>
+ <span style="display: inline-block; width: 128px;">@session.UserInfo.RoomCount rooms</span>
+ <a style="color: #888888" href="@("/ServerInfo/" + session.Homeserver.ServerName + "/")">@session.ServerVersion.Server.Name @session.ServerVersion.Server.Version</a>
@if (_auth.Proxy != null) {
<span class="badge badge-info"> (proxied via @_auth.Proxy)</span>
}
else {
<p>Not proxied</p>
}
- @if (DEBUG) {
- <p>T=@__auth.Homeserver.GetType().FullName</p>
- <p>D=@__auth.Homeserver.WhoAmI.DeviceId</p>
- <p>U=@__auth.Homeserver.WhoAmI.UserId</p>
+ @if (_debug) {
+ <p>T=@session.Homeserver.GetType().FullName</p>
+ <p>D=@session.Homeserver.WhoAmI.DeviceId</p>
+ <p>U=@session.Homeserver.WhoAmI.UserId</p>
}
</td>
<td>
@@ -49,18 +48,46 @@ Small collection of tools to do not-so-everyday things.
<LinkButton OnClick="@(() => RemoveUser(_auth, true))">Log out</LinkButton>
</p>
</td>
- @* </div> *@
</tr>
}
</table>
</form>
+@if (_offlineSessions.Count > 0) {
+ <br/>
+ <br/>
+ <h5>Sessions on unreachable servers</h5>
+ <hr/>
+ <form>
+ <table>
+ @foreach (var session in _offlineSessions) {
+ <tr class="user-entry">
+ <td>
+ <p>
+ @{
+ string[] parts = session.UserId.Split(':');
+ }
+ <span>@parts[0][1..]</span> on <span>@parts[1]</span>
+ @if (!string.IsNullOrWhiteSpace(session.Proxy)) {
+ <span class="badge badge-info"> (proxied via @session.Proxy)</span>
+ }
+ </p>
+ </td>
+ <td>
+ <LinkButton OnClick="@(() => RemoveUser(session))">Remove</LinkButton>
+ </td>
+ </tr>
+ }
+ </table>
+ </form>
+}
+
@code
{
#if DEBUG
- bool DEBUG = true;
+ private const bool _debug = true;
#else
- bool DEBUG = false;
+ private const bool _debug = false;
#endif
private class AuthInfo {
@@ -71,35 +98,42 @@ Small collection of tools to do not-so-everyday things.
}
// private Dictionary<UserAuth, UserInfo> _users = new();
- private List<AuthInfo> _auth = new();
+ private readonly List<AuthInfo> _sessions = [];
+ private readonly List<UserAuth> _offlineSessions = [];
+ private LoginResponse? _currentSession;
protected override async Task OnInitializedAsync() {
+ Console.WriteLine("Index.OnInitializedAsync");
_currentSession = await MRUStorage.GetCurrentToken();
- // _users.Clear();
- _auth.Clear();
+ _sessions.Clear();
+ _offlineSessions.Clear();
var tokens = await MRUStorage.GetAllTokens();
var profileTasks = tokens.Select(async token => {
UserInfo userInfo = new();
AuthenticatedHomeserverGeneric hs;
+ Console.WriteLine($"Getting hs for {token.ToJson()}");
try {
hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy);
}
catch (MatrixException e) {
- if (e.ErrorCode == "M_UNKNOWN_TOKEN") {
- NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken);
- return;
- }
- throw;
+ if (e.ErrorCode != "M_UNKNOWN_TOKEN") throw;
+ NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken);
+ return;
+
}
catch (HttpRequestException e) {
logger.LogError(e, $"Failed to instantiate AuthenticatedHomeserver for {token.ToJson()}, homeserver may be offline?", token.UserId);
+ _offlineSessions.Add(token);
return;
}
+
+ Console.WriteLine($"Got hs for {token.ToJson()}");
+
var roomCountTask = hs.GetJoinedRooms();
var profile = await hs.GetProfileAsync(hs.WhoAmI.UserId);
userInfo.DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId;
Console.WriteLine(profile.ToJson());
- _auth.Add(new() {
+ _sessions.Add(new() {
UserInfo = new() {
AvatarUrl = string.IsNullOrWhiteSpace(profile.AvatarUrl) ? "https://api.dicebear.com/6.x/identicon/svg?seed=" + hs.WhoAmI.UserId : hs.ResolveMediaUri(profile.AvatarUrl),
RoomCount = (await roomCountTask).Count,
@@ -110,7 +144,9 @@ Small collection of tools to do not-so-everyday things.
Homeserver = hs
});
});
+ Console.WriteLine("Waiting for profile tasks");
await Task.WhenAll(profileTasks);
+ Console.WriteLine("Done waiting for profile tasks");
await base.OnInitializedAsync();
}
@@ -127,19 +163,20 @@ Small collection of tools to do not-so-everyday things.
}
}
catch (Exception e) {
- if (e is MatrixException {ErrorCode: "M_UNKNOWN_TOKEN" }) {
- //todo: handle this
+ if (e is MatrixException { ErrorCode: "M_UNKNOWN_TOKEN" }) {
+ //todo: handle this
return;
}
+
Console.WriteLine(e);
}
+
await MRUStorage.RemoveToken(auth);
if ((await MRUStorage.GetCurrentToken())?.AccessToken == auth.AccessToken)
await MRUStorage.SetCurrentToken((await MRUStorage.GetAllTokens() ?? throw new InvalidOperationException()).FirstOrDefault());
await OnInitializedAsync();
}
- private LoginResponse _currentSession;
private async Task SwitchSession(UserAuth auth) {
Console.WriteLine($"Switching to {auth.Homeserver} {auth.UserId} via {auth.Proxy}");
diff --git a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor
index d33756b..506a8a1 100644
--- a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor
+++ b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor
@@ -4,6 +4,7 @@
@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.RoomTypes
@using ArcaneLibs.Extensions
+@using MatrixRoomUtils.Abstractions
<h3>UserRoomHistory</h3>
<span>Enter mxid: </span>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
index 2ac4bcb..6cabe82 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -1,25 +1,21 @@
@page "/Rooms"
@using LibMatrix.Filters
@using LibMatrix.Helpers
-@using LibMatrix.EventTypes.Spec.State
-@using LibMatrix
-@using LibMatrix.Homeservers
-@using ArcaneLibs.Extensions
@using LibMatrix.Extensions
@using LibMatrix.Responses
@using System.Collections.ObjectModel
@using System.Diagnostics
+@using ArcaneLibs.Extensions
+@using MatrixRoomUtils.Abstractions
@inject ILogger<Index> logger
<h3>Room list</h3>
<p>@Status</p>
<p>@Status2</p>
-@* @if (RenderContents) { *@
+
+<LinkButton href="/Rooms/Create">Create new room</LinkButton>
+
<RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList>
-@* } *@
-@* else { *@
-@* <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" StillFetching="true"></RoomList> *@
-@* } *@
@code {
private ObservableCollection<RoomInfo> Rooms { get; } = new();
@@ -47,9 +43,9 @@
},
State = new SyncFilter.RoomFilter.StateFilter {
Types = new List<string> {
+ "m.room.create",
"m.room.name",
"m.room.avatar",
- "m.room.create",
"org.matrix.mjolnir.shortcode",
"m.room.power_levels",
}
@@ -92,49 +88,72 @@
// }
// };
+ private SyncHelper syncHelper;
+
+ // SyncHelper profileSyncHelper;
+
protected override async Task OnInitializedAsync() {
Homeserver = await MRUStorage.GetCurrentSessionOrNavigate();
if (Homeserver is null) return;
var rooms = await Homeserver.GetJoinedRooms();
- foreach (var room in rooms) {
- Rooms.Add(new(){Room = room});
+ // SemaphoreSlim _semaphore = new(160, 160);
+
+ var roomTasks = rooms.Select(async room => {
+ RoomInfo ri;
+ // await _semaphore.WaitAsync();
+ ri = new() { Room = room };
+ await Task.WhenAll((filter.Room?.State?.Types ?? []).Select(x => ri.GetStateEvent(x)));
+ return ri;
+ }).ToAsyncEnumerable();
+
+ await foreach (var room in roomTasks) {
+ Rooms.Add(room);
+ StateHasChanged();
+ // await Task.Delay(50);
+ // _semaphore.Release();
}
-
- GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId);
- var syncHelper = new SyncHelper(Homeserver, logger) {
- Timeout = 10000,
+ if (rooms.Count >= 150) RenderContents = true;
+
+ GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId);
+ syncHelper = new SyncHelper(Homeserver, logger) {
+ Timeout = 30000,
Filter = filter,
MinimumDelay = TimeSpan.FromMilliseconds(5000)
};
- // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId);
- // var profileSyncHelper = new SyncHelper(Homeserver, logger) {
+ // profileSyncHelper = new SyncHelper(Homeserver, logger) {
// Timeout = 10000,
// Filter = profileUpdateFilter,
// MinimumDelay = TimeSpan.FromMilliseconds(5000)
- // };
+ // };
+ // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId);
+
RunSyncLoop(syncHelper);
// RunSyncLoop(profileSyncHelper);
RunQueueProcessor();
+
await base.OnInitializedAsync();
}
-
+
private async Task RunQueueProcessor() {
var renderTimeSw = Stopwatch.StartNew();
+ var isInitialSync = true;
while (true) {
try {
- if (queue.Count == 0) {
- while (queue.Count == 0) {
- Console.WriteLine("Queue is empty, waiting...");
- await Task.Delay(2500);
- }
- Console.WriteLine("Queue no longer empty!");
+ while (queue.Count == 0) {
+ Console.WriteLine("Queue is empty, waiting...");
+ await Task.Delay(isInitialSync ? 100 : 2500);
}
- while (queue.TryDequeue(out var queueEntry)) {
+
+ Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!");
+
+ int maxUpdates = 10;
+ isInitialSync = false;
+ while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) {
var (roomId, roomData) = queueEntry;
Console.WriteLine($"Dequeued room {roomId}");
RoomInfo room;
-
+
if (Rooms.Any(x => x.Room.RoomId == roomId)) {
room = Rooms.First(x => x.Room.RoomId == roomId);
Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events");
@@ -146,26 +165,23 @@
};
Rooms.Add(room);
}
-
+
if (room.StateEvents is null) {
Console.WriteLine($"QueueWorker: {roomId} does not have state events on record?");
throw new InvalidDataException("Somehow this is null???");
}
- if (roomData.State?.Events is {Count: >0 })
+
+ if (roomData.State?.Events is { Count: > 0 })
room.StateEvents.MergeStateEventLists(roomData.State.Events);
else {
Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!");
}
- if (Random.Shared.Next(101) < 20 || true) {
- Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue...";
- }
- RenderContents |= queue.Count == 0;
- if (queue.Count > 10) RenderContents = false;
- await Task.Delay(RenderContents ? 25 : 6);
}
- // else {
- // Console.WriteLine("Failed to dequeue item");
- // }
+ Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}");
+ Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue...";
+
+ RenderContents |= queue.Count == 0;
+ await Task.Delay(Rooms.Count);
}
catch (Exception e) {
Console.WriteLine("QueueWorker exception: " + e);
@@ -214,11 +230,15 @@
// We can't trust servers to give us what we ask for, and this ruins performance
// Thanks, Conduit.
joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.Types?.Contains(x.Type) == false);
- if(filter.Room?.State?.NotSenders?.Any() ?? false)
+ if (filter.Room?.State?.NotSenders?.Any() ?? false)
joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.NotSenders?.Contains(x.Sender) ?? false);
-
+
queue.Enqueue(joinedRoom);
}
+ if (sync.Rooms.Leave is {Count: > 0})
+ foreach (var leftRoom in sync.Rooms.Leave)
+ if (Rooms.Any(x => x.Room.RoomId == leftRoom.Key))
+ Rooms.Remove(Rooms.First(x => x.Room.RoomId == leftRoom.Key));
Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue... " +
$"{sync?.Rooms?.Join?.Count ?? 0} new updates!";
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
index 846d1cb..dbe0648 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor
@@ -4,184 +4,199 @@
@using ArcaneLibs.Extensions
@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.EventTypes.Spec.State.Policy
+@using System.Diagnostics
+@using System.Diagnostics.CodeAnalysis
+@using LibMatrix.Extensions
+@using LibMatrix.Responses
<h3>Policy list editor - Editing @RoomId</h3>
<hr/>
<p>
- This policy list contains @PolicyEvents.Count(x => x.Type == "m.policy.rule.server") server bans,
- @PolicyEvents.Count(x => x.Type == "m.policy.rule.room") room bans and
- @PolicyEvents.Count(x => x.Type == "m.policy.rule.user") user bans.
+ This policy list contains @GetPolicyCount(typeof(ServerPolicyRuleEventContent)) server bans,
+ @GetPolicyCount(typeof(RoomPolicyRuleEventContent)) room bans and
+ @GetPolicyCount(typeof(UserPolicyRuleEventContent)) user bans.
+ @foreach (var (key, value) in PolicyEventsByType) {
+ <p>@key.Name: @value.Count</p>
+ }
</p>
-<InputCheckbox @bind-Value="_enableAvatars" @oninput="GetAllAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label>
-
+<InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label>
-@if (!PolicyEvents.Any(x => x.Type == "m.policy.rule.server")) {
+<h3>Server policies</h3>
+<hr/>
+@if (!GetPolicyEventsByType(typeof(ServerPolicyRuleEventContent)).Any()) {
<p>No server policies</p>
}
else {
- <h3>Server policies</h3>
- <hr/>
- <table class="table table-striped table-hover" style="width: fit-Content;">
+ <table class="table table-striped table-hover" style="width: fit-content;">
<thead>
- <tr>
- <th scope="col" style="max-width: 50vw;">Server</th>
- <th scope="col">Reason</th>
- <th scope="col">Expires</th>
- <th scope="col">Actions</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
- var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
<tr>
- <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
- <td>@policyData.Reason</td>
- <td>
- @policyData.ExpiryDateTime
- </td>
- <td>
- <button class="btn" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Edit</button>
- @* <button class="btn btn-danger" $1$ @onclick="async () => await RemovePolicyAsync(policyEvent)" #1#>Remove</button> *@
- </td>
+ <th style="max-width: 50vw;">Server</th>
+ <th>Reason</th>
+ <th>Expires</th>
+ <th>Actions</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) {
+ var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
+ <tr>
+ <td>
+ <span>Entity: @policyData.Entity</span>
+ <span><br/>State: @policyEvent.StateKey</span>
+ </td>
+ <td>@policyData.Reason</td>
+ <td>
+ @policyData.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Edit</button>
+ @* <button class="btn btn-danger" $1$ @onclick="async () => await RemovePolicyAsync(policyEvent)" #1#>Remove</button> *@
+ </td>
+ </tr>
+ }
</tbody>
</table>
<details>
- <summary>Redacted events</summary>
- <table class="table table-striped table-hover" style="width: fit-Content;">
+ <summary>Redacted or invalid events</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
<thead>
- <tr>
- <th scope="col" style="max-width: 50vw;">State key</th>
- <th scope="col">Serialised Contents</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.server" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) {
- var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
<tr>
- <td>@policyEvent.StateKey</td>
- <td>@policyEvent.RawContent.ToJson(false, true)</td>
+ <th style="max-width: 50vw;">State key</th>
+ <th>Serialised Contents</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) {
+ <tr>
+ <td>@policyEvent.StateKey</td>
+ <td>@policyEvent.RawContent.ToJson(false, true)</td>
+ </tr>
+ }
</tbody>
</table>
</details>
}
-@if (!PolicyEvents.Any(x => x.Type == "m.policy.rule.room")) {
+<h3>Room policies</h3>
+<hr/>
+@if (!GetPolicyEventsByType(typeof(RoomPolicyRuleEventContent)).Any()) {
<p>No room policies</p>
}
else {
- <h3>Room policies</h3>
- <hr/>
- <table class="table table-striped table-hover" style="width: fit-Content;">
+ <table class="table table-striped table-hover" style="width: fit-content;">
<thead>
- <tr>
- <th scope="col" style="max-width: 50vw;">Room</th>
- <th scope="col">Reason</th>
- <th scope="col">Expires</th>
- <th scope="col">Actions</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
- var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
<tr>
- <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
- <td>@policyData.Reason</td>
- <td>
- @policyData.ExpiryDateTime
- </td>
- <td>
- <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
- </td>
+ <th style="max-width: 50vw;">Room</th>
+ <th>Reason</th>
+ <th>Expires</th>
+ <th>Actions</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) {
+ var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
+ <tr>
+ <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td>
+ <td>@policyData.Reason</td>
+ <td>
+ @policyData.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
</tbody>
</table>
<details>
- <summary>Redacted events</summary>
- <table class="table table-striped table-hover" style="width: fit-Content;">
+ <summary>Redacted or invalid events</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
<thead>
- <tr>
- <th scope="col" style="max-width: 50vw;">State key</th>
- <th scope="col">Serialised Contents</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.room" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) {
<tr>
- <td>@policyEvent.StateKey</td>
- <td>@policyEvent.RawContent!.ToJson(false, true)</td>
+ <th style="max-width: 50vw;">State key</th>
+ <th>Serialised Contents</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) {
+ <tr>
+ <td>@policyEvent.StateKey</td>
+ <td>@policyEvent.RawContent!.ToJson(false, true)</td>
+ </tr>
+ }
</tbody>
</table>
</details>
}
-@if (!PolicyEvents.Any(x => x.Type == "m.policy.rule.user")) {
+<h3>User policies</h3>
+<hr/>
+@if (!GetPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Any()) {
<p>No user policies</p>
}
else {
- <h3>User policies</h3>
- <hr/>
- <table class="table table-striped table-hover" style="width: fit-Content;">
+ <table class="table table-striped table-hover" style="width: fit-content;">
<thead>
- <tr>
- @if (_enableAvatars) {
- <th scope="col"></th>
- }
- <th scope="col" style="max-width: 0.2vw; word-wrap: anywhere;">User</th>
- <th scope="col">Reason</th>
- <th scope="col">Expires</th>
- <th scope="col">Actions</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
- var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
<tr>
- @if (_enableAvatars) {
- <td scope="col">
- <img style="width: 48px; height: 48px; aspect-ratio: unset; border-radius: 50%;" src="@(avatars.ContainsKey(policyData.Entity) ? avatars[policyData.Entity] : "")"/>
- </td>
+ @if (EnableAvatars) {
+ <th></th>
}
- <td style="word-wrap: anywhere;">Entity: @string.Join("", policyData.Entity.Take(64))<br/>State: @string.Join("", policyEvent.StateKey.Take(64))</td>
- <td>@policyData.Reason</td>
- <td>
- @policyData.ExpiryDateTime
- </td>
- <td>
- <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
- </td>
+ <th style="max-width: 0.2vw; word-wrap: anywhere;">User</th>
+ <th>Reason</th>
+ <th>Expires</th>
+ <th>Actions</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) {
+ var policyData = policyEvent.TypedContent as PolicyRuleEventContent;
+ <tr>
+ @if (EnableAvatars) {
+ <td>
+ @if (Avatars.ContainsKey(policyData.Entity)) {
+ <img class="avatar48" src="@Avatars[policyData.Entity]"/>
+ }
+ </td>
+ }
+ <td style="word-wrap: anywhere;">Entity: @string.Join("", policyData.Entity.Take(64))<br/>State: @string.Join("", policyEvent.StateKey.Take(64))</td>
+ <td>@policyData.Reason</td>
+ <td>
+ @policyData.ExpiryDateTime
+ </td>
+ <td>
+ <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button>
+ </td>
+ </tr>
+ }
</tbody>
</table>
<details>
- <summary>Redacted events</summary>
- <table class="table table-striped table-hover" style="width: fit-Content;">
+ <summary>Redacted or invalid events</summary>
+ <table class="table table-striped table-hover" style="width: fit-content;">
<thead>
- <tr>
- <th scope="col">State key</th>
- <th scope="col">Serialised Contents</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity == null)) {
<tr>
- <td>@policyEvent.StateKey</td>
- <td>@policyEvent.RawContent.ToJson(false, true)</td>
+ <th>State key</th>
+ <th>Serialised Contents</th>
</tr>
- }
+ </thead>
+ <tbody>
+ @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) {
+ <tr>
+ <td>@policyEvent.StateKey</td>
+ <td>@policyEvent.RawContent.ToJson(false, true)</td>
+ </tr>
+ }
</tbody>
</table>
</details>
}
-<LogView></LogView>
-
@code {
+
+#if DEBUG
+ private const bool Debug = true;
+#else
+ private const bool Debug = false;
+#endif
+
//get room list
// - sync withroom list filter
// Type = support.feline.msc3784
@@ -192,18 +207,28 @@ else {
private bool _enableAvatars;
- static readonly Dictionary<string, string?> avatars = new();
- static readonly Dictionary<string, RemoteHomeserver> servers = new();
+ static readonly Dictionary<string, string?> Avatars = new();
+ // static readonly Dictionary<string, RemoteHomeserver> Servers = new();
- public static List<StateEventResponse> PolicyEvents { get; set; } = new();
+ // private static List<StateEventResponse> PolicyEvents { get; set; } = new();
+ private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
+
+ public bool EnableAvatars {
+ get => _enableAvatars;
+ set {
+ _enableAvatars = value;
+ if (value) GetAllAvatars();
+ }
+ }
protected override async Task OnInitializedAsync() {
+ var sw = Stopwatch.StartNew();
await base.OnInitializedAsync();
var hs = await MRUStorage.GetCurrentSessionOrNavigate();
if (hs is null) return;
RoomId = RoomId.Replace('~', '.');
await LoadStatesAsync();
- Console.WriteLine("Policy list editor initialized!");
+ Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!");
}
private async Task LoadStatesAsync() {
@@ -214,39 +239,52 @@ else {
var states = room.GetFullStateAsync();
await foreach (var state in states) {
- if (!state.Type.StartsWith("m.policy.rule")) continue;
- PolicyEvents.Add(state);
+ if (state is null) continue;
+ if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue;
+ if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new());
+ PolicyEventsByType[state.MappedType].Add(state);
}
-
- // var stateEventsQuery = await room.GetStateAsync("");
- // var stateEvents = stateEventsQuery.Value.Deserialize<List<StateEventResponse>>();
- // PolicyEvents = stateEvents.Where(x => x.Type.StartsWith("m.policy.rule"))
- // .Select(x => JsonSerializer.Deserialize<StateEventResponse>(JsonSerializer.Serialize(x))).ToList();
StateHasChanged();
}
- private async Task GetAvatar(string userId) {
- try {
- if (avatars.ContainsKey(userId)) return;
- var hs = userId.Split(':')[1];
- var server = servers.ContainsKey(hs) ? servers[hs] : new RemoteHomeserver(userId.Split(':')[1]);
- if (!servers.ContainsKey(hs)) servers.Add(hs, server);
- var profile = await server.GetProfileAsync(userId);
- avatars.Add(userId, await hsResolver.ResolveMediaUri(server.BaseUrl, profile.AvatarUrl));
- servers.Add(userId, server);
+ private async Task GetAllAvatars() {
+ // if (!_enableAvatars) return;
+ Console.WriteLine("Getting avatars...");
+ var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue<string>()).Where(x => x.Contains(':') && !x.Contains("*")).ToList();
+ Console.WriteLine($"Got {users.Count} users!");
+ var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList());
+ Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!");
+ var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable();
+ await foreach (var server in homeserverTasks) {
+ if (server is null) continue;
+ var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList();
+ await Task.WhenAll(profileTasks);
+ profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } });
+ foreach (var profile in profileTasks.Select(x => x.Result!.Value)) {
+ // if (profile is null) continue;
+ if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) {
+ var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl);
+ Avatars.TryAdd(profile.Key, url);
+ }
+ else Avatars.TryAdd(profile.Key, null);
+ }
StateHasChanged();
}
- catch {
- // ignored
- }
}
- private async Task GetAllAvatars() {
- foreach (var policyEvent in PolicyEvents.Where(x => x.Type == "m.policy.rule.user" && (x.TypedContent as PolicyRuleEventContent).Entity is not null)) {
- await GetAvatar((policyEvent.TypedContent as PolicyRuleEventContent).Entity);
+ private async Task<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) {
+ try {
+ return new KeyValuePair<string, UserProfileResponse>(mxid, await server.GetProfileAsync(mxid));
+ }
+ catch {
+ return null;
}
- StateHasChanged();
}
-}
+ private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
+ private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList();
+ private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList();
+ private int GetPolicyCount(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type].Count : 0;
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/User/DMManager.razor b/MatrixRoomUtils.Web/Pages/User/DMManager.razor
index 1b28516..a327793 100644
--- a/MatrixRoomUtils.Web/Pages/User/DMManager.razor
+++ b/MatrixRoomUtils.Web/Pages/User/DMManager.razor
@@ -1,7 +1,7 @@
@page "/User/DirectMessages"
-@using LibMatrix.Homeservers
@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.Responses
+@using MatrixRoomUtils.Abstractions
<h3>Direct Messages</h3>
<hr/>
diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor
index 553f46d..60c68ac 100644
--- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor
+++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor
@@ -7,6 +7,7 @@
@using MatrixRoomUtils.LibDMSpace.StateEvents
@using ArcaneLibs.Extensions
@using System.Text.Json.Serialization
+@using MatrixRoomUtils.Abstractions
<b>
<u>DM Space setup tool - stage 2: Fix DM room attribution</u>
</b>
@@ -185,9 +186,9 @@ else {
}
catch { }
- var membersEnum = room.GetMembersAsync();
+ var membersEnum = room.GetMembersEnumerableAsync(true);
await foreach (var member in membersEnum)
- if (member.TypedContent is RoomMemberEventContent memberEvent && !string.IsNullOrWhiteSpace(memberEvent.Membership) && memberEvent.Membership == "join")
+ if (member.TypedContent is RoomMemberEventContent memberEvent)
roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey });
if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) {
diff --git a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor
index 854b09c..42573e6 100644
--- a/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor
+++ b/MatrixRoomUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor
@@ -7,6 +7,8 @@
@using MatrixRoomUtils.LibDMSpace.StateEvents
@using ArcaneLibs.Extensions
@using System.Text.Json.Serialization
+@using MatrixRoomUtils.Abstractions
+
<b>
<u>DM Space setup tool - stage 3: Preview space layout</u>
</b>
@@ -154,9 +156,9 @@ else {
}
catch { }
- var membersEnum = room.GetMembersAsync();
+ var membersEnum = room.GetMembersEnumerableAsync(true);
await foreach (var member in membersEnum)
- if (member.TypedContent is RoomMemberEventContent memberEvent && !string.IsNullOrWhiteSpace(memberEvent.Membership) && memberEvent.Membership == "join")
+ if (member.TypedContent is RoomMemberEventContent memberEvent)
roomMembers.Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey });
if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) {
diff --git a/MatrixRoomUtils.Web/Pages/User/Profile.razor b/MatrixRoomUtils.Web/Pages/User/Profile.razor
index ae3fb76..73d7c6e 100644
--- a/MatrixRoomUtils.Web/Pages/User/Profile.razor
+++ b/MatrixRoomUtils.Web/Pages/User/Profile.razor
@@ -9,40 +9,43 @@
@if (NewProfile is not null) {
<h4>Profile</h4>
<hr/>
-
- <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/>
- <div style="display: inline-block; vertical-align: middle;">
- <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/>
- <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox>
- <InputFile OnChange="@AvatarChanged"></InputFile><br/>
- <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton>
- <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
+ <div>
+ <img src="@Homeserver.ResolveMediaUri(NewProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/>
+ <div style="display: inline-block; vertical-align: middle;">
+ <span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/>
+ <span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox>
+ <InputFile OnChange="@AvatarChanged"></InputFile><br/>
+ <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton>
+ <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
+ </div>
</div>
@if (!string.IsNullOrWhiteSpace(Status)) {
<p>@Status</p>
}
- <details>
- <summary style="font-size: 1.5rem;">Room profiles<hr></summary>
-
- @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x=>RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) {
- <details class="details-compact">
- <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary>
- <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/>
- <div style="display: inline-block; vertical-align: middle;">
- <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/>
- <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox>
- <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/>
- <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton>
- </div>
- <br/>
- @if (!string.IsNullOrWhiteSpace(Status)) {
- <p>@Status</p>
- }
- </details>
+ <br/>
+
+ @* <details> *@
+ <h4>Room profiles<hr></h4>
+
+ @foreach (var (roomId, roomProfile) in RoomProfiles.OrderBy(x => RoomNames.TryGetValue(x.Key, out var _name) ? _name : x.Key)) {
+ <details class="details-compact">
+ <summary style="@(roomProfile.DisplayName == OldProfile.DisplayName && roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")">@(RoomNames.TryGetValue(roomId, out var name) ? name : roomId)</summary>
+ <img src="@Homeserver.ResolveMediaUri(roomProfile.AvatarUrl)" style="width: 96px; height: 96px; border-radius: 50%; object-fit: cover;"/>
+ <div style="display: inline-block; vertical-align: middle;">
+ <span>Display name: </span><FancyTextBox BackgroundColor="@(roomProfile.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@roomProfile.DisplayName"></FancyTextBox><br/>
+ <span>Avatar URL: </span><FancyTextBox BackgroundColor="@(roomProfile.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@roomProfile.AvatarUrl"></FancyTextBox>
+ <InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, roomId))"></InputFile><br/>
+ <LinkButton OnClick="@(() => UpdateRoomProfile(roomId))">Update profile</LinkButton>
+ </div>
<br/>
- }
- </details>
+ @if (!string.IsNullOrWhiteSpace(Status)) {
+ <p>@Status</p>
+ }
+ </details>
+ <br/>
+ }
+ // </details>
}
@code {
@@ -54,7 +57,10 @@
private string? Status {
get => _status;
- set { _status = value; StateHasChanged(); }
+ set {
+ _status = value;
+ StateHasChanged();
+ }
}
private Dictionary<string, RoomMemberEventContent> RoomProfiles { get; set; } = new();
@@ -65,14 +71,15 @@
if (Homeserver is null) return;
Status = "Loading global profile...";
if (Homeserver.WhoAmI?.UserId is null) return;
- NewProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)).DeepClone();
- OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)).DeepClone();
+ NewProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone();
+ OldProfile = (await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId)); //.DeepClone();
Status = "Loading room profiles...";
var roomProfiles = Homeserver.GetRoomProfilesAsync();
await foreach (var (roomId, roomProfile) in roomProfiles) {
// Status = $"Got profile for {roomId}...";
- RoomProfiles[roomId] = roomProfile.DeepClone();
+ RoomProfiles[roomId] = roomProfile; //.DeepClone();
}
+
StateHasChanged();
Status = "Room profiles loaded, loading room names...";
@@ -80,6 +87,7 @@
var name = await x.GetNameOrFallbackAsync();
return new KeyValuePair<string, string?>(x.RoomId, name);
}).ToAsyncEnumerable();
+
await foreach (var (roomId, roomName) in roomNameTasks) {
// Status = $"Got room name for {roomId}: {roomName}";
RoomNames[roomId] = roomName;
@@ -106,13 +114,14 @@
StateHasChanged();
await OnInitializedAsync();
}
+
private async Task RoomAvatarChanged(InputFileChangeEventArgs arg, string roomId) {
var res = await Homeserver.UploadFile(arg.File.Name, arg.File.OpenReadStream(Int64.MaxValue), arg.File.ContentType);
Console.WriteLine(res);
RoomProfiles[roomId].AvatarUrl = res;
StateHasChanged();
}
-
+
private async Task UpdateRoomProfile(string roomId) {
Status = "Busy processing room profile update, please do not leave this page...";
StateHasChanged();
@@ -122,5 +131,4 @@
StateHasChanged();
}
-}
-
+}
\ No newline at end of file
|