diff --git a/MatrixUtils.Web/Pages/Rooms/Create.razor b/MatrixUtils.Web/Pages/Rooms/Create.razor
index f2dfb01..021ad18 100644
--- a/MatrixUtils.Web/Pages/Rooms/Create.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Create.razor
@@ -3,11 +3,9 @@
@using System.Reflection
@using ArcaneLibs.Extensions
@using LibMatrix
-@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using LibMatrix.Responses
@using MatrixUtils.Web.Classes.RoomCreationTemplates
-@using Microsoft.AspNetCore.Components.Forms
@* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@
<h3>Room Manager - Create Room</h3>
@@ -89,7 +87,7 @@
<tr>
<td>Room icon:</td>
<td>
- <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/>
+ @* <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> *@
<div style="display: inline-block; vertical-align: middle;">
<FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox><br/>
<InputFile OnChange="RoomIconFilePicked"></InputFile>
@@ -134,7 +132,7 @@
}
else {
<details>
- <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Allow.Count) allow rules</summary>
+ <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerAclEventContent).Allow.Count) allow rules</summary>
@* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@
</details>
}
@@ -144,7 +142,7 @@
}
else {
<details>
- <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Deny.Count) deny rules</summary>
+ <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerAclEventContent).Deny.Count) deny rules</summary>
@* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@
</details>
}
@@ -256,11 +254,11 @@
private RoomHistoryVisibilityEventContent? historyVisibility => creationEvent?["m.room.history_visibility"].TypedContent as RoomHistoryVisibilityEventContent;
private RoomGuestAccessEventContent? guestAccessEvent => creationEvent?["m.room.guest_access"].TypedContent as RoomGuestAccessEventContent;
- private RoomServerACLEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as RoomServerACLEventContent;
+ private RoomServerAclEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as RoomServerAclEventContent;
private RoomAvatarEventContent? roomAvatarEvent => creationEvent?["m.room.avatar"].TypedContent as RoomAvatarEventContent;
protected override async Task OnInitializedAsync() {
- Homeserver = await RMUStorage.GetCurrentSessionOrNavigate();
+ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (Homeserver is null) return;
foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList()) {
diff --git a/MatrixUtils.Web/Pages/Rooms/Index.razor b/MatrixUtils.Web/Pages/Rooms/Index.razor
index 28c4de2..0373a46 100644
--- a/MatrixUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Index.razor
@@ -13,9 +13,7 @@
<p>@Status2</p>
<LinkButton href="/Rooms/Create">Create new room</LinkButton>
-<CascadingValue TValue="AuthenticatedHomeserverGeneric" Value="Homeserver">
- <RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList>
-</CascadingValue>
+<RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents" Homeserver="@Homeserver"></RoomList>
@code {
@@ -68,7 +66,7 @@
// SyncHelper profileSyncHelper;
protected override async Task OnInitializedAsync() {
- Homeserver = await RMUStorage.GetCurrentSessionOrNavigate();
+ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (Homeserver is null) return;
// var rooms = await Homeserver.GetJoinedRooms();
// SemaphoreSlim _semaphore = new(160, 160);
@@ -122,7 +120,7 @@
try {
while (queue.Count == 0) {
Console.WriteLine("Queue is empty, waiting...");
- await Task.Delay(isInitialSync ? 100 : 2500);
+ await Task.Delay(isInitialSync ? 1000 : 2500);
}
Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!");
@@ -131,15 +129,15 @@
isInitialSync = false;
while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) {
var (roomId, roomData) = queueEntry;
- Console.WriteLine($"Dequeued room {roomId}");
+ // 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");
+ // Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events");
}
else {
- Console.WriteLine($"QueueWorker: encountered new room {roomId}!");
+ // Console.WriteLine($"QueueWorker: encountered new room {roomId}!");
room = new RoomInfo(Homeserver.GetRoom(roomId), roomData.State?.Events);
Rooms.Add(room);
}
@@ -155,6 +153,11 @@
Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!");
}
+ if (maxUpdates % 100 == 0) {
+ Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}");
+ StateHasChanged();
+ await Task.Yield();
+ }
// await Task.Delay(100);
}
@@ -170,7 +173,7 @@
}
}
- private bool RenderContents { get; set; } = false;
+ private bool RenderContents { get; set; }
private string _status;
@@ -225,9 +228,10 @@
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!";
+ $"{sync.Rooms?.Join?.Count ?? 0} new updates!";
- Status2 = $"Next batch: {sync.NextBatch}";
+ Status2 = $"Next batch: {sync?.NextBatch}";
+ await Task.Yield();
}
}
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index b7ebae2..9c35673 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -1,7 +1,6 @@
@page "/Rooms/{RoomId}/Policies"
@using LibMatrix
@using ArcaneLibs.Extensions
-@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.EventTypes.Spec.State.Policy
@using System.Diagnostics
@using LibMatrix.RoomTypes
@@ -9,13 +8,25 @@
@using System.Reflection
@using ArcaneLibs.Attributes
@using LibMatrix.EventTypes
+@using LibMatrix.EventTypes.Common
+@using LibMatrix.EventTypes.Interop.Draupnir
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
@using MatrixUtils.Web.Shared.PolicyEditorComponents
+@using SpawnDev.BlazorJS.WebWorkers
+@inject WebWorkerService WebWorkerService
-<h3>Policy list editor - Editing @RoomId</h3>
+<h3>Policy list editor - Editing @(RoomName ?? RoomId)</h3>
+@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) {
+ <span style="margin-right: 2em;">Shortcode: @DraupnirShortcode</span>
+}
+@if (!string.IsNullOrWhiteSpace(RoomAlias)) {
+ <span>Alias: @RoomAlias</span>
+}
<hr/>
@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
+<LinkButton OnClick="@(() => { MassCreatePolicies = true; return Task.CompletedTask; })">Create many new policies</LinkButton>
@if (Loading) {
<p>Loading...</p>
@@ -24,6 +35,8 @@ else if (PolicyEventsByType is not { Count: > 0 }) {
<p>No policies yet</p>
}
else {
+ var renderSw = Stopwatch.StartNew();
+ var renderTotalSw = Stopwatch.StartNew();
@foreach (var (type, value) in PolicyEventsByType) {
<p>
@(GetValidPolicyEventsByType(type).Count) active,
@@ -33,6 +46,8 @@ else {
</p>
}
+ Console.WriteLine($"Rendered hearder in {renderSw.GetElapsedAndRestart()}");
+
@foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) {
<details>
<summary>
@@ -41,7 +56,7 @@ else {
</span>
<hr style="margin: revert;"/>
</summary>
- <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;">
+ <table class="table table-striped table-hover">
@{
var policies = GetValidPolicyEventsByType(type);
var invalidPolicies = GetInvalidPolicyEventsByType(type);
@@ -51,13 +66,18 @@ else {
.Where(x => x.GetCustomAttribute<TableHideAttribute>() is null)
.ToFrozenSet();
var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet();
+
+ var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => props.Any(y => y.Name == x.Name))
+ .ToFrozenSet();
+ Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}");
}
<thead>
<tr>
@foreach (var name in propNames) {
- <th style="border-width: 1px">@name</th>
+ <th>@name</th>
}
- <th style="border-width: 1px">Actions</th>
+ <th>Actions</th>
</tr>
</thead>
<tbody style="border-width: 1px;">
@@ -65,10 +85,6 @@ else {
<tr>
@{
var typedContent = policy.TypedContent!;
- var proxySafeProps = typedContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Where(x => props.Any(y => y.Name == x.Name))
- .ToFrozenSet();
- Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}");
}
@foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) {
<td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td>
@@ -81,6 +97,16 @@ else {
@if (policy.IsLegacyType) {
<LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
}
+
+ @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.Type)) {
+ <LinkButton OnClick="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent</LinkButton>
+ @if (CurrentUserIsDraupnir) {
+ <LinkButton Color="@(ActiveKicks.ContainsKey(policy) ? "#FF0000" : null)" OnClick="@(() => DraupnirKickMatching(policy))">Kick users @(ActiveKicks.ContainsKey(policy) ? $"({ActiveKicks[policy]})" : null)</LinkButton>
+ }
+ }
+ }
+ else {
+ <p>No permission to modify</p>
}
</div>
</td>
@@ -94,11 +120,11 @@ else {
@("Invalid " + GetPolicyTypeName(type).ToLower())
</u>
</summary>
- <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;">
+ <table class="table table-striped table-hover">
<thead>
<tr>
- <th style="border-width: 1px">State key</th>
- <th style="border-width: 1px">Json contents</th>
+ <th>State key</th>
+ <th>Json contents</th>
</tr>
</thead>
<tbody>
@@ -115,12 +141,25 @@ else {
</details>
</details>
}
+
+ Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}");
+ Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}");
}
@if (CurrentlyEditingEvent is not null) {
<PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal>
}
+@if (ServerPolicyToMakePermanent is not null) {
+ <ModalWindow Title="Make policy permanent">
+
+ </ModalWindow>
+}
+
+@if (MassCreatePolicies) {
+ <MassPolicyEditorModal Room="@Room" OnClose="@(() => MassCreatePolicies = false)" OnSaved="@(() => { MassCreatePolicies = false; LoadStatesAsync(); })"></MassPolicyEditorModal>
+}
+
@code {
#if DEBUG
@@ -130,21 +169,15 @@ else {
#endif
private bool Loading { get; set; } = true;
- //get room list
- // - sync withroom list filter
- // Type = support.feline.msc3784
- //support.feline.policy.lists.msc.v1
[Parameter]
- public string RoomId { get; set; } = null!;
+ public string RoomId { get; set; }
private bool _enableAvatars;
private StateEventResponse? _currentlyEditingEvent;
+ private bool _massCreatePolicies;
+ private StateEventResponse? _serverPolicyToMakePermanent;
- // static readonly Dictionary<string, string?> Avatars = new();
- // static readonly Dictionary<string, RemoteHomeserver> Servers = new();
-
- // private static List<StateEventResponse> PolicyEvents { get; set; } = new();
private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
private StateEventResponse? CurrentlyEditingEvent {
@@ -155,25 +188,44 @@ else {
}
}
- // public bool EnableAvatars {
- // get => _enableAvatars;
- // set {
- // _enableAvatars = value;
- // if (value) GetAllAvatars();
- // }
- // }
+ public StateEventResponse? ServerPolicyToMakePermanent {
+ get => _serverPolicyToMakePermanent;
+ set {
+ _serverPolicyToMakePermanent = value;
+ StateHasChanged();
+ }
+ }
private AuthenticatedHomeserverGeneric Homeserver { get; set; }
private GenericRoom Room { get; set; }
private RoomPowerLevelEventContent PowerLevels { get; set; }
+ public bool CurrentUserIsDraupnir { get; set; }
+ public string? RoomName { get; set; }
+ public string? RoomAlias { get; set; }
+ public string? DraupnirShortcode { get; set; }
+ public Dictionary<StateEventResponse, int> ActiveKicks { get; set; } = [];
+
+ public bool MassCreatePolicies {
+ get => _massCreatePolicies;
+ set {
+ _massCreatePolicies = value;
+ StateHasChanged();
+ }
+ }
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;
Room = Homeserver.GetRoom(RoomId!);
- PowerLevels = (await Room.GetPowerLevelsAsync())!;
+ await Task.WhenAll([
+ Task.Run(async () => { PowerLevels = (await Room.GetPowerLevelsAsync())!; }),
+ Task.Run(async () => { DraupnirShortcode = (await Room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode; }),
+ Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }),
+ Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }),
+ Task.Run(async () => { CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync<object>("org.matrix.mjolnir.protected_rooms")) is not null; }),
+ ]);
await LoadStatesAsync();
Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!");
}
@@ -193,48 +245,13 @@ else {
StateHasChanged();
}
- // 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();
- // }
- // }
- //
- // 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;
- // }
- // }
-
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();
+ .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList();
+ .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull()
?? type.GetCustomAttributes<MatrixEventAttribute>()
@@ -265,4 +282,139 @@ else {
private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
.ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x);
+ private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes
+ .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray());
+
+#region Draupnir interop
+
+ private SemaphoreSlim ss = new(16, 16);
+
+ private async Task DraupnirKickMatching(StateEventResponse policy) {
+ try {
+ var content = policy.TypedContent! as PolicyRuleEventContent;
+ if (content is null) return;
+ if (string.IsNullOrWhiteSpace(content.Entity)) return;
+
+ var data = await Homeserver.GetAccountDataAsync<DraupnirProtectedRoomsData>(DraupnirProtectedRoomsData.EventId);
+ var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList();
+
+ ActiveKicks.Add(policy, rooms.Count);
+ StateHasChanged();
+ await Task.Delay(500);
+
+ // for (int i = 0; i < 12; i++) {
+ // _ = WebWorkerService.TaskPool.Invoke(WasteCpu);
+ // }
+
+ // static async Task runKicks(string roomId, PolicyRuleEventContent content) {
+ // Console.WriteLine($"Checking {roomId}...");
+ // // Console.WriteLine($"Checking {room.RoomId}...");
+ // //
+ // // try {
+ // // var members = await room.GetMembersListAsync();
+ // // foreach (var member in members) {
+ // // var membership = member.ContentAs<RoomMemberEventContent>();
+ // // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue;
+ // // if (membership?.Membership is "leave" or "ban") continue;
+ // //
+ // // if (content.EntityMatches(member.StateKey!))
+ // // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
+ // // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
+ // // }
+ // // }
+ // // finally {
+ // // Console.WriteLine($"Finished checking {room.RoomId}...");
+ // // }
+ // }
+ //
+ // try {
+ // var tasks = rooms.Select(room => WebWorkerService.TaskPool.Invoke(runKicks, room.RoomId, content)).ToList();
+ //
+ // await Task.WhenAll(tasks);
+ // }
+ // catch (Exception e) {
+ // Console.WriteLine(e);
+ // }
+
+ await NastyInternalsPleaseIgnore.ExecuteKickWithWasmWorkers(WebWorkerService, Homeserver, policy, data.Rooms);
+ // await Task.Run(async () => {
+ // foreach (var room in rooms) {
+ // try {
+ // Console.WriteLine($"Checking {room.RoomId}...");
+ // var members = await room.GetMembersListAsync();
+ // foreach (var member in members) {
+ // var membership = member.ContentAs<RoomMemberEventContent>();
+ // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue;
+ // if (membership?.Membership is "leave" or "ban") continue;
+ //
+ // if (content.EntityMatches(member.StateKey!))
+ // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
+ // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
+ // }
+ // ActiveKicks[policy]--;
+ // StateHasChanged();
+ // }
+ // finally {
+ // Console.WriteLine($"Finished checking {room.RoomId}...");
+ // }
+ // }
+ // });
+ }
+ finally {
+ ActiveKicks.Remove(policy);
+ StateHasChanged();
+ await Task.Delay(500);
+ }
+ }
+
+#region Nasty, nasty internals, please ignore!
+
+ private static class NastyInternalsPleaseIgnore {
+ public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List<string> roomIds) {
+ try {
+ // var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList();
+ var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList();
+ // workerService.TaskPool.Invoke(ExecuteKickInternal, hs.BaseUrl, hs.AccessToken, roomIds, content.Entity);
+ await Task.WhenAll(tasks);
+ }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ }
+ }
+
+ private static async Task ExecuteKickInternal(string homeserverBaseUrl, string accessToken, string roomId, string entity) {
+ try {
+ Console.WriteLine("args: " + string.Join(", ", homeserverBaseUrl, accessToken, roomId, entity));
+ Console.WriteLine($"Checking {roomId}...");
+ var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken);
+ Console.WriteLine($"Got HS...");
+ var room = hs.GetRoom(roomId);
+ Console.WriteLine($"Got room...");
+ var members = await room.GetMembersListAsync();
+ Console.WriteLine($"Got members...");
+ // foreach (var member in members) {
+ // var membership = member.ContentAs<RoomMemberEventContent>();
+ // if (member.StateKey == hs.WhoAmI.UserId) continue;
+ // if (membership?.Membership is "leave" or "ban") continue;
+ //
+ // if (entity == member.StateKey)
+ // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given");
+ // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)");
+ // }
+ }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ }
+ }
+
+ private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) {
+ Console.WriteLine($"Checking {roomId}...");
+ Console.WriteLine(policy.EventId);
+ }
+ }
+
+#endregion
+
+#endregion
+
}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css
new file mode 100644
index 0000000..afe9fb0
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css
@@ -0,0 +1,9 @@
+th {
+ border-width: 1px;
+}
+
+table {
+ width: fit-content;
+ border-width: 1px;
+ vertical-align: middle;
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
new file mode 100644
index 0000000..982fc5a
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor
@@ -0,0 +1,240 @@
+@page "/Rooms/{RoomId}/Policies2"
+@using LibMatrix
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State.Policy
+@using System.Diagnostics
+@using LibMatrix.RoomTypes
+@using System.Collections.Frozen
+@using System.Reflection
+@using ArcaneLibs.Attributes
+@using LibMatrix.EventTypes
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
+
+@using MatrixUtils.Web.Shared.PolicyEditorComponents
+
+<h3>Policy list editor - Editing @RoomId</h3>
+<hr/>
+@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
+<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
+
+@if (Loading) {
+ <p>Loading...</p>
+}
+else if (PolicyEventsByType is not { Count: > 0 }) {
+ <p>No policies yet</p>
+}
+else {
+ var renderSw = Stopwatch.StartNew();
+ var renderTotalSw = Stopwatch.StartNew();
+ @foreach (var (type, value) in PolicyEventsByType) {
+ <p>
+ @(GetValidPolicyEventsByType(type).Count) active,
+ @(GetInvalidPolicyEventsByType(type).Count) invalid
+ (@value.Count total)
+ @(GetPolicyTypeName(type).ToLower())
+ </p>
+ }
+
+ Console.WriteLine($"Rendered hearder in {renderSw.GetElapsedAndRestart()}");
+
+ @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) {
+ <details>
+ <summary>
+ <span>
+ @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies")
+ </span>
+ <hr style="margin: revert;"/>
+ </summary>
+ <div class="flex-grid">
+ @{
+ var policies = GetValidPolicyEventsByType(type);
+ var invalidPolicies = GetInvalidPolicyEventsByType(type);
+ // enumerate all properties with friendly name
+ var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null)
+ .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null)
+ .ToFrozenSet();
+ var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet();
+
+ var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => props.Any(y => y.Name == x.Name))
+ .ToFrozenSet();
+ Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}");
+ }
+ @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) {
+ <div class="flex-item">
+ @{
+ var typedContent = policy.TypedContent!;
+ }
+ @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) {
+ <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td>
+ }
+ <div style="display: ruby;">
+ @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) {
+ <LinkButton OnClick="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton>
+ <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
+ @if (policy.IsLegacyType) {
+ <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
+ }
+
+ @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) {
+ <LinkButton OnClick="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton>
+ @if (CurrentUserIsDraupnir) {
+ <LinkButton OnClick="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton>
+ }
+ }
+ else {
+ <p>meow</p>
+ }
+ }
+ else {
+ <p>No permission to modify</p>
+ }
+ </div>
+ </div>
+ }
+ </div>
+ <details>
+ <summary>
+ <u>
+ @("Invalid " + GetPolicyTypeName(type).ToLower())
+ </u>
+ </summary>
+ <table class="table table-striped table-hover">
+ <thead>
+ <tr>
+ <th>State key</th>
+ <th>Json contents</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policy in invalidPolicies) {
+ <tr>
+ <td>@policy.StateKey</td>
+ <td>
+ <pre>@policy.RawContent.ToJson(true, false)</pre>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </details>
+ </details>
+ }
+
+ Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}");
+ Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}");
+}
+
+@if (CurrentlyEditingEvent is not null) {
+ <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal>
+}
+
+@code {
+
+#if DEBUG
+ private const bool Debug = true;
+#else
+ private const bool Debug = false;
+#endif
+
+ private bool Loading { get; set; } = true;
+
+ [Parameter]
+ public string RoomId { get; set; }
+
+ private bool _enableAvatars;
+ private StateEventResponse? _currentlyEditingEvent;
+ private StateEventResponse? _serverPolicyToMakePermanent;
+
+ private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new();
+
+ private StateEventResponse? CurrentlyEditingEvent {
+ get => _currentlyEditingEvent;
+ set {
+ _currentlyEditingEvent = value;
+ StateHasChanged();
+ }
+ }
+
+ private StateEventResponse? ServerPolicyToMakePermanent {
+ get => _serverPolicyToMakePermanent;
+ set {
+ _serverPolicyToMakePermanent = value;
+ StateHasChanged();
+ }
+ }
+
+ private AuthenticatedHomeserverGeneric Homeserver { get; set; }
+ private GenericRoom Room { get; set; }
+ private RoomPowerLevelEventContent PowerLevels { get; set; }
+ private bool CurrentUserIsDraupnir { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ var sw = Stopwatch.StartNew();
+ await base.OnInitializedAsync();
+ Homeserver = (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!;
+ if (Homeserver is null) return;
+ Room = Homeserver.GetRoom(RoomId!);
+ PowerLevels = (await Room.GetPowerLevelsAsync())!;
+ CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync<object>("org.matrix.mjolnir.protected_rooms")) is not null;
+ await LoadStatesAsync();
+ Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!");
+ }
+
+ private async Task LoadStatesAsync() {
+ Loading = true;
+ var states = Room.GetFullStateAsync();
+ PolicyEventsByType.Clear();
+ await foreach (var state in states) {
+ 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);
+ }
+
+ Loading = false;
+ 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?["recommendation"]?.GetValue<string>())).ToList();
+
+ private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
+
+ private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull()
+ ?? type.GetCustomAttributes<MatrixEventAttribute>()
+ .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.EventName))?.EventName;
+
+ private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name;
+
+ private async Task RemovePolicyAsync(StateEventResponse policyEvent) {
+ await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), new { });
+ PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent);
+ await LoadStatesAsync();
+ }
+
+ private async Task UpdatePolicyAsync(StateEventResponse policyEvent) {
+ await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), policyEvent.RawContent);
+ CurrentlyEditingEvent = null;
+ await LoadStatesAsync();
+ }
+
+ private async Task UpgradePolicyAsync(StateEventResponse policyEvent) {
+ policyEvent.RawContent["upgraded_from_type"] = policyEvent.Type;
+ await LoadStatesAsync();
+ }
+
+ private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
+
+ // event types, unnamed
+ private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes
+ .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x);
+
+ private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes
+ .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray());
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css
new file mode 100644
index 0000000..d224737
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css
@@ -0,0 +1,32 @@
+th {
+ border-width: 1px;
+}
+
+table {
+ width: fit-content;
+ border-width: 1px;
+ vertical-align: middle;
+}
+
+.flex-grid {
+ display: grid;
+ /*grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));*/
+ /*// fit based on content max width*/
+ grid-template-columns: repeat(auto-fill, minmax(min-content, 1fr));
+
+ gap: 10px;
+}
+
+.flex-item {
+ /*flex: 1 1 30%;*/
+ /*margin: 0.25rem;*/
+ /*position: relative;*/
+ /*display: flex;*/
+ /*flex-direction: column;*/
+ min-width: 0;
+ word-wrap: break-word;
+ background-color: #fff1;
+ background-clip: border-box;
+ border: 1px solid rgba(0, 0, 0, .125);
+ border-radius: .5rem
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
new file mode 100644
index 0000000..2903ab8
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor
@@ -0,0 +1,176 @@
+@page "/PolicyLists"
+@using ArcaneLibs.Extensions
+@using LibMatrix
+@using LibMatrix.EventTypes
+@using LibMatrix.EventTypes.Common
+@using LibMatrix.EventTypes.Spec.State.Policy
+@using LibMatrix.RoomTypes
+@inject ILogger<Index> logger
+<h3>Policy lists </h3> @* <LinkButton href="/Rooms/Create">Create new policy list</LinkButton> *@
+
+@if (!string.IsNullOrWhiteSpace(Status)) {
+ <p>@Status</p>
+}
+@if (!string.IsNullOrWhiteSpace(Status2)) {
+ <p>@Status2</p>
+}
+<hr/>
+
+<table>
+ <thead>
+ <tr>
+ <th/>
+ <th>Room name</th>
+ <th>Policies</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var room in Rooms.OrderByDescending(x => x.PolicyCounts.Sum(y => y.Value))) {
+ <tr>
+ <td>
+ <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">
+ <span class="oi oi-pencil" aria-hidden="true"></span>
+ </LinkButton>
+ </td>
+ <td style="padding-right: 24px;">
+ <span>@room.RoomName</span>
+ @if (room.IsLegacy) {
+ <span style="color: red;"> (legacy)</span>
+ }
+ <br/>
+ @if (!string.IsNullOrWhiteSpace(room.Shortcode)) {
+ <span style="font-size: 0.8em;">@room.Shortcode</span>
+ }
+ else {
+ <span style="color: red;">(no shortcode)</span>
+ }
+ </td>
+ <td>
+ <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.User) ?? 0) user policies</span><br/>
+ <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Server) ?? 0) server policies</span><br/>
+ <span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Room) ?? 0) room policies</span><br/>
+ </td>
+ </tr>
+ }
+ </tbody>
+</table>
+
+@code {
+
+ private List<RoomInfo> Rooms { get; } = [];
+
+ private AuthenticatedHomeserverGeneric? Homeserver { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
+ if (Homeserver is null) return;
+
+ Status = "Fetching rooms...";
+
+ var userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
+ var serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
+ var roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
+ var knownPolicyTypes = (List<string>) [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
+
+ List<GenericRoom> roomsByType = [];
+ await foreach (var room in Homeserver.GetJoinedRoomsByType("support.feline.policy.lists.msc.v1")) {
+ roomsByType.Add(room);
+ Status2 = $"Found {room.RoomId} (MSC3784)...";
+ }
+
+ List<Task<RoomInfo>> tasks = roomsByType.Select(async room => {
+ Status2 = $"Fetching room {room.RoomId}...";
+ return await RoomInfo.FromRoom(room);
+ }).ToList();
+
+ var results = tasks.ToAsyncEnumerable();
+ await foreach (var result in results) {
+ Rooms.Add(result);
+ StateHasChanged();
+ }
+
+ Status = "Searching for legacy lists...";
+
+ var rooms = (await Homeserver.GetJoinedRooms())
+ .Where(x => !Rooms.Any(y => y.Room.RoomId == x.RoomId))
+ .Select(async room => {
+ var state = await room.GetFullStateAsListAsync();
+ var policies = state
+ .Where(x => knownPolicyTypes.Contains(x.Type))
+ .ToList();
+ if (policies.Count == 0) return null;
+ Status2 = $"Found legacy list {room.RoomId}...";
+ return await RoomInfo.FromRoom(room, state, true);
+ })
+ .ToAsyncEnumerable();
+
+ await foreach (var room in rooms) {
+ if (room is not null) {
+ Rooms.Add(room);
+ StateHasChanged();
+ }
+ }
+
+ Status = "";
+ Status2 = "";
+ await base.OnInitializedAsync();
+ }
+
+ private string _status;
+
+ public string Status {
+ get => _status;
+ set {
+ _status = value;
+ StateHasChanged();
+ }
+ }
+
+ private string _status2;
+
+ public string Status2 {
+ get => _status2;
+ set {
+ _status2 = value;
+ StateHasChanged();
+ }
+ }
+
+ private class RoomInfo {
+ public GenericRoom Room { get; set; }
+ public string RoomName { get; set; }
+ public string? Shortcode { get; set; }
+ public Dictionary<PolicyType, int?> PolicyCounts { get; set; }
+ public bool IsLegacy { get; set; }
+
+ public enum PolicyType {
+ User,
+ Room,
+ Server
+ }
+
+ private static readonly List<string> userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
+ private static readonly List<string> serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
+ private static readonly List<string> roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
+ private static readonly List<string> allKnownPolicyTypes = [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
+
+ public static async Task<RoomInfo> FromRoom(GenericRoom room, List<StateEventResponse>? state = null, bool legacy = false) {
+ state ??= await room.GetFullStateAsListAsync();
+ return new RoomInfo() {
+ Room = room,
+ IsLegacy = legacy,
+ RoomName = await room.GetNameAsync()
+ ?? (await room.GetCanonicalAliasAsync())?.Alias
+ ?? (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode
+ ?? room.RoomId,
+ Shortcode = (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode,
+ PolicyCounts = new() {
+ { PolicyType.User, state.Count(x => userEventTypes.Contains(x.Type)) },
+ { PolicyType.Server, state.Count(x => serverEventTypes.Contains(x.Type)) },
+ { PolicyType.Room, state.Count(x => roomEventTypes.Contains(x.Type)) }
+ }
+ };
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css
new file mode 100644
index 0000000..f9b5b3f
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css
@@ -0,0 +1,6 @@
+table, th, td {
+ border-width: 1px;
+}
+td {
+ padding: 8px;
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor
index 01ab1c4..46e39ed 100644
--- a/MatrixUtils.Web/Pages/Rooms/Space.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Space.razor
@@ -2,11 +2,15 @@
@using LibMatrix.RoomTypes
@using ArcaneLibs.Extensions
@using LibMatrix
+@using MatrixUtils.Abstractions
<h3>Room manager - Viewing Space</h3>
+<span>Add new room to space: </span>
+<FancyTextBox @bind-Value="@NewRoomId"></FancyTextBox>
+<button onclick="@AddNewRoom">Add</button>
<button onclick="@JoinAllRooms">Join all rooms</button>
@foreach (var room in Rooms) {
- <RoomListItem Room="room" ShowOwnProfile="true"></RoomListItem>
+ <RoomListItem RoomInfo="room" ShowOwnProfile="true"></RoomListItem>
}
@@ -27,11 +31,12 @@
private GenericRoom? Room { get; set; }
private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>();
- private List<GenericRoom> Rooms { get; } = new();
+ private List<RoomInfo> Rooms { get; } = new();
private List<string> ServersInSpace { get; } = new();
+ 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;
Room = hs.GetRoom(RoomId.Replace('~', '.'));
@@ -43,7 +48,18 @@
var roomId = stateEvent.StateKey;
var room = hs.GetRoom(roomId);
if (room is not null) {
- Rooms.Add(room);
+ Task.Run(async () => {
+ try {
+ Rooms.Add(new(Room, await room.GetFullStateAsListAsync()));
+ }
+ catch (MatrixException e) {
+ if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) {
+ Rooms.Add(new(Room) {
+ RoomName = "M_FORBIDDEN"
+ });
+ }
+ }
+ });
}
break;
}
@@ -96,8 +112,37 @@
// List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList();
// await Task.WhenAll(tasks);
foreach (var room in Rooms) {
+ await JoinRecursive(room.Room.RoomId);
+ }
+ }
+
+ private async Task JoinRecursive(string roomId) {
+ var room = Room!.Homeserver.GetRoom(roomId);
+ if (room is null) return;
+ try {
await room.JoinAsync(ServersInSpace.ToArray());
+ var joined = false;
+ while (!joined) {
+ var ce = await room.GetCreateEventAsync();
+ if(ce is null) continue;
+ if (ce.Type == "m.space") {
+ var children = room.AsSpace.GetChildrenAsync(false);
+ await foreach (var child in children) {
+ JoinRecursive(child.RoomId);
+ }
+ }
+ joined = true;
+ }
}
+ catch (Exception e) {
+ Console.WriteLine(e);
+ }
+
+ }
+
+ private async Task AddNewRoom() {
+ if (string.IsNullOrWhiteSpace(NewRoomId)) return;
+ await Room.AsSpace.AddChildByIdAsync(NewRoomId);
}
}
diff --git a/MatrixUtils.Web/Pages/Rooms/StateEditor.razor b/MatrixUtils.Web/Pages/Rooms/StateEditor.razor
index 6110b83..51cb265 100644
--- a/MatrixUtils.Web/Pages/Rooms/StateEditor.razor
+++ b/MatrixUtils.Web/Pages/Rooms/StateEditor.razor
@@ -43,7 +43,7 @@
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
RoomId = RoomId.Replace('~', '.');
await LoadStatesAsync();
@@ -53,7 +53,7 @@
private DateTime _lastUpdate = DateTime.Now;
private async Task LoadStatesAsync() {
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
var StateLoaded = 0;
var response = (hs.GetRoom(RoomId)).GetFullStateAsync();
diff --git a/MatrixUtils.Web/Pages/Rooms/StateViewer.razor b/MatrixUtils.Web/Pages/Rooms/StateViewer.razor
index 7c31136..c8b87d3 100644
--- a/MatrixUtils.Web/Pages/Rooms/StateViewer.razor
+++ b/MatrixUtils.Web/Pages/Rooms/StateViewer.razor
@@ -70,7 +70,7 @@
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
await LoadStatesAsync();
Console.WriteLine("Policy list editor initialized!");
@@ -80,7 +80,7 @@
private async Task LoadStatesAsync() {
var StateLoaded = 0;
- var hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (hs is null) return;
var response = (hs.GetRoom(RoomId)).GetFullStateAsync();
await foreach (var _ev in response) {
diff --git a/MatrixUtils.Web/Pages/Rooms/Timeline.razor b/MatrixUtils.Web/Pages/Rooms/Timeline.razor
index e6b1248..108581c 100644
--- a/MatrixUtils.Web/Pages/Rooms/Timeline.razor
+++ b/MatrixUtils.Web/Pages/Rooms/Timeline.razor
@@ -2,7 +2,7 @@
@using MatrixUtils.Web.Shared.TimelineComponents
@using LibMatrix
@using LibMatrix.EventTypes.Spec
-@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.EventTypes.Spec.State.RoomInfo
<h3>RoomManagerTimeline</h3>
<hr/>
<p>Loaded @Events.Count events...</p>
@@ -27,7 +27,7 @@
protected override async Task OnInitializedAsync() {
Console.WriteLine("RoomId: " + RoomId);
- Homeserver = await RMUStorage.GetCurrentSessionOrNavigate();
+ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true);
if (Homeserver is null) return;
var room = Homeserver.GetRoom(RoomId);
MessagesResponse? msgs = null;
|