From 4601c2f1fcdaf551705a9da3c5e6ddfc3eff9138 Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 22 Nov 2024 06:42:27 +0100 Subject: Some fixes all over, policy list work --- .../MainTabComponents/MainTabSpaceItem.razor | 2 +- .../Index2Components/RoomsIndex2MainTab.razor | 37 ++-- MatrixUtils.Web/Pages/Rooms/PolicyList.razor | 191 +++++++++++++++++++- MatrixUtils.Web/Pages/Rooms/PolicyList2.razor | 27 +++ .../Draupnir/DraupnirProtectedRoomsEditor.razor | 10 +- .../Pages/Tools/Moderation/FindUsersByRegex.razor | 193 +++++++++++++++++++++ 6 files changed, 430 insertions(+), 30 deletions(-) create mode 100644 MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor (limited to 'MatrixUtils.Web/Pages') diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor index 6483f01..596d63d 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/MainTabComponents/MainTabSpaceItem.razor @@ -8,7 +8,7 @@ } - + @Space.RoomName @if (IsSpaceOpened()) { diff --git a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor index 7ccfae2..d2b6d5a 100644 --- a/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor +++ b/MatrixUtils.Web/Pages/Labs/Rooms2/Index2Components/RoomsIndex2MainTab.razor @@ -22,26 +22,27 @@ @* *@ @* *@ -
-
-
- Uncategorised rooms - @foreach (var space in GetTopLevelSpaces()) { - @* @RecursingSpaceChildren(space) *@ - - } -
-
-

Placeholder for rooms list...

- @if (SelectedSpace != null) { - foreach (var room in GetSpaceChildRooms(SelectedSpace)) { -

@room.RoomName

+ +
+
+
+ Uncategorised rooms + @foreach (var space in GetTopLevelSpaces()) { + @* @RecursingSpaceChildren(space) *@ + + } +
+
+

Placeholder for rooms list...

+ @if (SelectedSpace != null) { + foreach (var room in GetSpaceChildRooms(SelectedSpace)) { +

@room.RoomName

+ } } - } +
-
- + @code { @@ -118,7 +119,7 @@ var childSpaces = children.Where(x => x.RoomType == "m.space").ToList(); return childSpaces; } - + private List GetSpaceChildRooms(RoomInfo space) { var children = GetSpaceChildren(space); var childRooms = children.Where(x => x.RoomType != "m.space").ToList(); diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor index d57aa43..b2ce9c3 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor @@ -6,13 +6,25 @@ @using System.Diagnostics @using LibMatrix.RoomTypes @using System.Collections.Frozen +@using System.Numerics @using System.Reflection +@using System.Runtime.InteropServices.JavaScript @using ArcaneLibs.Attributes @using LibMatrix.EventTypes +@using LibMatrix.EventTypes.Common +@using LibMatrix.EventTypes.Interop.Draupnir @using MatrixUtils.Web.Shared.PolicyEditorComponents +@using SpawnDev.BlazorJS.WebWorkers +@inject WebWorkerService WebWorkerService -

Policy list editor - Editing @RoomId

+

Policy list editor - Editing @(RoomName ?? RoomId)

+@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) { + Shortcode: @DraupnirShortcode +} +@if (!string.IsNullOrWhiteSpace(RoomAlias)) { + Alias: @RoomAlias +}
@* *@ Create new policy @@ -56,10 +68,10 @@ else { .Where(x => x.GetCustomAttribute() 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(); + .ToFrozenSet(); Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); } @@ -87,6 +99,16 @@ else { @if (policy.IsLegacyType) { Update policy type } + + @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.Type)) { + Make permanent + @if (CurrentUserIsDraupnir) { + Kick users @(ActiveKicks.ContainsKey(policy) ? $"({ActiveKicks[policy]})" : null) + } + } + } + else { +

No permission to modify

}
@@ -130,6 +152,12 @@ else { } +@if (ServerPolicyToMakePermanent is not null) { + + + +} + @if (MassCreatePolicies) { } @@ -150,6 +178,7 @@ else { private bool _enableAvatars; private StateEventResponse? _currentlyEditingEvent; private bool _massCreatePolicies; + private StateEventResponse? _serverPolicyToMakePermanent; private Dictionary> PolicyEventsByType { get; set; } = new(); @@ -161,9 +190,22 @@ else { } } + 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 ActiveKicks { get; set; } = []; public bool MassCreatePolicies { get => _massCreatePolicies; @@ -179,7 +221,13 @@ else { Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; 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.EventId))?.Shortcode; }), + Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }), + Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }), + Task.Run(async () => { CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync("org.matrix.mjolnir.protected_rooms")) is not null; }), + ]); await LoadStatesAsync(); Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); } @@ -236,4 +284,139 @@ else { private static Dictionary PolicyTypes = KnownPolicyTypes .ToDictionary(x => x.GetCustomAttributes().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + private static Dictionary PolicyTypeIds = KnownPolicyTypes + .ToDictionary(x => x, x => x.GetCustomAttributes().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.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(); + // // 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(); + // 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 async static Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List 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 async static 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(); + // 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/PolicyList2.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor index adac385..d5d0a7e 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor @@ -76,6 +76,19 @@ else { @if (policy.IsLegacyType) { Update policy type } + + @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) { + Make permanent (wildcard) + @if (CurrentUserIsDraupnir) { + Kick matching users + } + } + else { +

meow

+ } + } + else { +

No permission to modify

} @@ -132,6 +145,7 @@ else { private bool _enableAvatars; private StateEventResponse? _currentlyEditingEvent; + private StateEventResponse? _serverPolicyToMakePermanent; private Dictionary> PolicyEventsByType { get; set; } = new(); @@ -143,9 +157,18 @@ else { } } + 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(); @@ -154,6 +177,7 @@ else { if (Homeserver is null) return; Room = Homeserver.GetRoom(RoomId!); PowerLevels = (await Room.GetPowerLevelsAsync())!; + CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync("org.matrix.mjolnir.protected_rooms")) is not null; await LoadStatesAsync(); Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); } @@ -210,4 +234,7 @@ else { private static Dictionary PolicyTypes = KnownPolicyTypes .ToDictionary(x => x.GetCustomAttributes().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + private static Dictionary PolicyTypeIds = KnownPolicyTypes + .ToDictionary(x => x, x => x.GetCustomAttributes().Select(y => y.EventName).ToArray()); + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor index 8745459..51f8e1b 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor @@ -3,6 +3,7 @@ @page "/Tools/Moderation/Draupnir/ProtectedRoomsEditor" @using System.Text.Json.Serialization @using LibMatrix +@using LibMatrix.EventTypes.Interop.Draupnir @using LibMatrix.EventTypes.Spec.State @using LibMatrix.RoomTypes

Edit Draupnir protected rooms

@@ -64,7 +65,7 @@ protected override async Task OnInitializedAsync() { hs = await RMUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - data = await hs.GetAccountDataAsync("org.matrix.mjolnir.protected_rooms"); + data = await hs.GetAccountDataAsync(DraupnirProtectedRoomsData.EventId); StateHasChanged(); var tasks = (await hs.GetJoinedRooms()).Select(async room => { var plTask = room.GetPowerLevelsAsync(); @@ -121,12 +122,7 @@ StateHasChanged(); } - - private class DraupnirProtectedRoomsData { - [JsonPropertyName("rooms")] - public List Rooms { get; set; } = new(); - } - + private class EditorRoomInfo { public GenericRoom Room { get; set; } public bool IsProtected { get; set; } diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor new file mode 100644 index 0000000..2d78f4e --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor @@ -0,0 +1,193 @@ +@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 +@using LibMatrix.Filters +@using LibMatrix.Helpers +@using LibMatrix.Utilities +

Find users by regex

+
+ +

Users (regex):

+ + +Execute +
+Remove kicks +Remove bans +
+ + +
+ Results + @foreach (var (userId, events) in matches) { +

@userId

+
    + @foreach (var match in events) { +
  • +
      +
    • @match.RoomName (@match.Room.RoomId)
    • +
    • Membership: @(match.Event.RawContent.ToJson(indent: false)) (sent by @match.Event.Sender)
    • +
    +
  • + } +
+ } +
+ +
+@foreach (var line in log.Reverse()) { +
@line
+} + +@code { + + private ObservableCollection log { get; set; } = new(); + + // List rooms { get; set; } = new(); + List rooms { get; set; } = []; + Dictionary> 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 UserIDs { get; set; } = new(); + + private AuthenticatedHomeserverGeneric hs { get; set; } + + protected override async Task OnInitializedAsync() { + log.CollectionChanged += (sender, args) => StateHasChanged(); + log.Add("Authenticating"); + hs = await RMUStorage.GetCurrentSessionOrNavigate(); + if (hs is null) return; + + StateHasChanged(); + Console.WriteLine("Rerendered!"); + await base.OnInitializedAsync(); + } + + private async Task 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 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()!.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()!.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 -- cgit 1.5.1