From 8bbe8c20b6b376bfe73fa8d8186e253cd351a642 Mon Sep 17 00:00:00 2001 From: Rory& Date: Sun, 22 Sep 2024 20:38:53 +0200 Subject: Changes --- MatrixUtils.Web/Pages/Rooms/PolicyList.razor | 101 ++++------ MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css | 9 + MatrixUtils.Web/Pages/Rooms/PolicyList2.razor | 213 +++++++++++++++++++++ MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css | 32 ++++ MatrixUtils.Web/Pages/Rooms/Space.razor | 43 ++++- MatrixUtils.Web/Pages/StreamTest.razor | 20 +- .../Pages/Tools/Moderation/MassCMEBan.razor | 51 +++-- .../Pages/Tools/Moderation/MembershipHistory.razor | 129 +++++++------ MatrixUtils.Web/Shared/MainLayout.razor.css | 1 + .../MassPolicyEditorModal.razor | 102 ++++++++++ .../PolicyEditorComponents/PolicyEditorModal.razor | 60 ++++-- 11 files changed, 594 insertions(+), 167 deletions(-) create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyList2.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyList2.razor.css create mode 100644 MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor (limited to 'MatrixUtils.Web') diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor index b7ebae2..4dc2adc 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor @@ -16,6 +16,7 @@
@* *@ Create new policy +Create many new policies @if (Loading) {

Loading...

@@ -24,6 +25,8 @@ else if (PolicyEventsByType is not { Count: > 0 }) {

No policies yet

} else { + var renderSw = Stopwatch.StartNew(); + var renderTotalSw = Stopwatch.StartNew(); @foreach (var (type, value) in PolicyEventsByType) {

@(GetValidPolicyEventsByType(type).Count) active, @@ -33,6 +36,8 @@ else {

} + Console.WriteLine($"Rendered hearder in {renderSw.GetElapsedAndRestart()}"); + @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) {
@@ -41,7 +46,7 @@ else {
- +
@{ var policies = GetValidPolicyEventsByType(type); var invalidPolicies = GetInvalidPolicyEventsByType(type); @@ -51,13 +56,18 @@ 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(); + Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); } @foreach (var name in propNames) { - + } - + @@ -65,10 +75,6 @@ else { @{ 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()) { @@ -94,11 +100,11 @@ else { @("Invalid " + GetPolicyTypeName(type).ToLower()) -
@name@nameActionsActions
@prop.GetGetMethod()?.Invoke(typedContent, null)
+
- - + + @@ -115,12 +121,19 @@ else { } + + Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); + Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}"); } @if (CurrentlyEditingEvent is not null) { } +@if (MassCreatePolicies) { + +} + @code { #if DEBUG @@ -130,21 +143,14 @@ 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!; private bool _enableAvatars; private StateEventResponse? _currentlyEditingEvent; + private bool _massCreatePolicies; - // static readonly Dictionary Avatars = new(); - // static readonly Dictionary Servers = new(); - - // private static List PolicyEvents { get; set; } = new(); private Dictionary> PolicyEventsByType { get; set; } = new(); private StateEventResponse? CurrentlyEditingEvent { @@ -155,18 +161,18 @@ else { } } - // public bool EnableAvatars { - // get => _enableAvatars; - // set { - // _enableAvatars = value; - // if (value) GetAllAvatars(); - // } - // } - private AuthenticatedHomeserverGeneric Homeserver { get; set; } private GenericRoom Room { get; set; } private RoomPowerLevelEventContent PowerLevels { get; set; } + public bool MassCreatePolicies { + get => _massCreatePolicies; + set { + _massCreatePolicies = value; + StateHasChanged(); + } + } + protected override async Task OnInitializedAsync() { var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); @@ -193,48 +199,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()).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?> TryGetProfile(RemoteHomeserver server, string mxid) { - // try { - // return new KeyValuePair(mxid, await server.GetProfileAsync(mxid)); - // } - // catch { - // return null; - // } - // } - private List GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; private List GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue())).ToList(); + .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); private List GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue())).ToList(); + .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() ?? type.GetCustomAttributes() @@ -243,13 +214,13 @@ else { private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name; private async Task RemovePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, new { }); + 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, policyEvent.RawContent); + await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey.UrlEncode(), policyEvent.RawContent); CurrentlyEditingEvent = null; await LoadStatesAsync(); } 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..adac385 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor @@ -0,0 +1,213 @@ +@page "/Rooms/{RoomId}/Policies2" +@using LibMatrix +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@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 MatrixUtils.Web.Shared.PolicyEditorComponents + +

Policy list editor - Editing @RoomId

+
+@* *@ +Create new policy + +@if (Loading) { +

Loading...

+} +else if (PolicyEventsByType is not { Count: > 0 }) { +

No policies yet

+} +else { + var renderSw = Stopwatch.StartNew(); + var renderTotalSw = Stopwatch.StartNew(); + @foreach (var (type, value) in PolicyEventsByType) { +

+ @(GetValidPolicyEventsByType(type).Count) active, + @(GetInvalidPolicyEventsByType(type).Count) invalid + (@value.Count total) + @(GetPolicyTypeName(type).ToLower()) +

+ } + + Console.WriteLine($"Rendered hearder in {renderSw.GetElapsedAndRestart()}"); + + @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { +
+ + + @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") + +
+
+
+ @{ + 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() 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())) { +
+ @{ + var typedContent = policy.TypedContent!; + } + @foreach (var prop in proxySafeProps ?? Enumerable.Empty()) { +
+ } +
+ @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { + Edit + Remove + @if (policy.IsLegacyType) { + Update policy type + } + } +
+ + } + +
+ + + @("Invalid " + GetPolicyTypeName(type).ToLower()) + + +
State keyJson contentsState keyJson contents
@prop.GetGetMethod()?.Invoke(typedContent, null)
+ + + + + + + + @foreach (var policy in invalidPolicies) { + + + + + } + +
State keyJson contents
@policy.StateKey +
@policy.RawContent.ToJson(true, false)
+
+
+ + } + + Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); + Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}"); +} + +@if (CurrentlyEditingEvent is not null) { + +} + +@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; } = null!; + + private bool _enableAvatars; + private StateEventResponse? _currentlyEditingEvent; + + private Dictionary> PolicyEventsByType { get; set; } = new(); + + private StateEventResponse? CurrentlyEditingEvent { + get => _currentlyEditingEvent; + set { + _currentlyEditingEvent = value; + StateHasChanged(); + } + } + + private AuthenticatedHomeserverGeneric Homeserver { get; set; } + private GenericRoom Room { get; set; } + private RoomPowerLevelEventContent PowerLevels { get; set; } + + protected override async Task OnInitializedAsync() { + var sw = Stopwatch.StartNew(); + await base.OnInitializedAsync(); + Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; + if (Homeserver is null) return; + Room = Homeserver.GetRoom(RoomId!); + PowerLevels = (await Room.GetPowerLevelsAsync())!; + 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 GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; + + private List GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); + + private List GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); + + private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() + ?? type.GetCustomAttributes() + .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 KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + // event types, unnamed + private static Dictionary PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + +} \ 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/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor index 01ab1c4..8153224 100644 --- a/MatrixUtils.Web/Pages/Rooms/Space.razor +++ b/MatrixUtils.Web/Pages/Rooms/Space.razor @@ -1,12 +1,14 @@ @page "/Rooms/{RoomId}/Space" +@using System.Collections.ObjectModel @using LibMatrix.RoomTypes @using ArcaneLibs.Extensions @using LibMatrix +@using MatrixUtils.Abstractions

Room manager - Viewing Space

@foreach (var room in Rooms) { - + } @@ -27,7 +29,7 @@ private GenericRoom? Room { get; set; } private StateEventResponse[] States { get; set; } = Array.Empty(); - private List Rooms { get; } = new(); + private List Rooms { get; } = new(); private List ServersInSpace { get; } = new(); protected override async Task OnInitializedAsync() { @@ -43,7 +45,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 +109,32 @@ // List> 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(true); + await foreach (var child in children) { + JoinRecursive(child.RoomId); + } + } + joined = true; + } + } + catch (Exception e) { + Console.WriteLine(e); } + } } diff --git a/MatrixUtils.Web/Pages/StreamTest.razor b/MatrixUtils.Web/Pages/StreamTest.razor index 57d3557..541cfe8 100644 --- a/MatrixUtils.Web/Pages/StreamTest.razor +++ b/MatrixUtils.Web/Pages/StreamTest.razor @@ -11,8 +11,8 @@ @* *@
- @foreach (var stream in Streams.OrderBy(x=>x.GetHashCode())) { - + @foreach (var stream in Streams.OrderBy(x => x.GetHashCode())) { + } } @@ -47,20 +47,34 @@ var roomState = await Homeserver.GetRoom("!dSMpkVKGgQHlgBDSpo:matrix.org").GetFullStateAsListAsync(); var members = roomState.Where(x => x.Type == RoomMemberEventContent.EventId).ToList(); Console.WriteLine($"Got {members.Count()} members"); + var ss = new SemaphoreSlim(1, 1); foreach (var stateEventResponse in members) { // Console.WriteLine(stateEventResponse.ToJson()); var mc = stateEventResponse.TypedContent as RoomMemberEventContent; if (!string.IsNullOrWhiteSpace(mc?.AvatarUrl)) { var uri = mc.AvatarUrl[6..].Split('/'); var url = $"/_matrix/media/v3/download/{uri[0]}/{uri[1]}"; + // Homeserver.GetMediaStreamAsync(mc?.AvatarUrl).ContinueWith(async x => { + // await ss.WaitAsync(); + // var stream = x.Result; + // Streams.Add(stream); + // StateHasChanged(); + await Task.Delay(100); + // ss.Release(); + // }); try { Homeserver.ClientHttpClient.GetStreamAsync(url).ContinueWith(async x => { + // await ss.WaitAsync(); var stream = x.Result; Streams.Add(stream); StateHasChanged(); + // await Task.Delay(100); + // ss.Release(); }); } - catch { } + catch (Exception e) { + Console.WriteLine(e); + } } } } diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor index ea1e5f6..a4e3918 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor @@ -1,6 +1,7 @@ @page "/Tools/Moderation/MassCMEBan" @using System.Collections.ObjectModel @using LibMatrix.EventTypes.Spec.State.Policy +@using LibMatrix.RoomTypes

User Trace


@@ -17,19 +18,19 @@ } @code { + // TODO: Properly implement page to be more useful private ObservableCollection log { get; set; } = new(); private AuthenticatedHomeserverGeneric hs { get; set; } - + [Parameter, SupplyParameterFromQuery(Name = "room")] public string roomId { get; set; } - protected override async Task OnInitializedAsync() { log.CollectionChanged += (sender, args) => StateHasChanged(); hs = await RMUStorage.GetCurrentSessionOrNavigate(); if (hs is null) return; - + StateHasChanged(); Console.WriteLine("Rerendered!"); await base.OnInitializedAsync(); @@ -37,33 +38,41 @@ private async Task Execute() { var room = hs.GetRoom("!fTjMjIzNKEsFlUIiru:neko.dev"); - // var room = hs.GetRoom("!yf7OpOiRDXx6zUGpT6:conduit.rory.gay"); - var users = roomId.Split("\n").Select(x => x.Trim()).Where(x=>x.StartsWith('@')).ToList(); - foreach (var user in users) { - var exists = false; - try { - exists = !string.IsNullOrWhiteSpace((await room.GetStateAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'))).Entity); - } catch (Exception e) { - log.Add($"Failed to get {user}"); - } + // var room = hs.GetRoom("!IVSjKMsVbjXsmUTuRR:rory.gay"); + var users = roomId.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList(); + var tasks = users.Select(x => ExecuteBan(room, x)).ToList(); + await Task.WhenAll(tasks); + + StateHasChanged(); + + return ""; + } - if (!exists) { + private async Task ExecuteBan(GenericRoom room, string user) { + var exists = false; + try { + exists = !string.IsNullOrWhiteSpace((await room.GetStateAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'))).Entity); + } + catch (Exception e) { + log.Add($"Failed to get {user}"); + } + + if (!exists) { + try { var evt = await room.SendStateEventAsync(UserPolicyRuleEventContent.EventId, user.Replace('@', '_'), new UserPolicyRuleEventContent() { Entity = user, - Reason = "spam (invite)", + Reason = "spam", Recommendation = "m.ban" }); log.Add($"Sent {evt.EventId} to ban {user}"); } - else { - log.Add($"User {user} already exists"); + catch (Exception e) { + log.Add($"Failed to ban {user}: {e}"); } } - - - StateHasChanged(); - - return ""; + else { + log.Add($"User {user} already exists"); + } } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor index e5ba004..94afc9a 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor @@ -55,75 +55,92 @@ if (ChronologicalOrder) { filteredMemberships = filteredMemberships.Reverse(); } - if(!string.IsNullOrWhiteSpace(Sender)) { + + if (!string.IsNullOrWhiteSpace(Sender)) { filteredMemberships = filteredMemberships.Where(x => x.Sender == Sender); } - if(!string.IsNullOrWhiteSpace(User)) { + + if (!string.IsNullOrWhiteSpace(User)) { filteredMemberships = filteredMemberships.Where(x => x.StateKey == User); } + @foreach (var membership in filteredMemberships) { RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent; - @switch (content.Membership) { - case RoomMemberEventContent.MembershipTypes.Invite: { - if (_showInvites) { -

@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

- } - - break; - } - case RoomMemberEventContent.MembershipTypes.Ban: { - if (_showBans) { -

@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

- } + StateEventResponse? previous = previousMemberships.GetValueOrDefault(membership.StateKey); + RoomMemberEventContent? previousContent = previous?.TypedContent as RoomMemberEventContent; + + + + previousMemberships[membership.StateKey] = membership; } - } +
@DateTimeOffset.FromUnixTimeMilliseconds(membership.OriginServerTs ?? 0).ToString("g") + @switch (content.Membership) { + case RoomMemberEventContent.MembershipTypes.Invite: { + if (_showInvites) { +

@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + + break; + } + case RoomMemberEventContent.MembershipTypes.Ban: { + if (_showBans) { +

@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } - break; - } - case RoomMemberEventContent.MembershipTypes.Leave: { - if (membership.Sender == membership.StateKey) { - if (_showLeaves) { -

@membership.Sender left the room

+ break; } - } - else { - if (_showKicks) { -

@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ case RoomMemberEventContent.MembershipTypes.Leave: { + if (membership.Sender == membership.StateKey) { + if (_showLeaves) { +

@membership.Sender left the room

+ } + } + else { + if (_showKicks) { +

@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } + } + + break; } - } - - break; - } - case RoomMemberEventContent.MembershipTypes.Knock: { - if (_showKnocks) { -

@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

- } + case RoomMemberEventContent.MembershipTypes.Knock: { + if (_showKnocks) { +

@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ } - break; - } - case RoomMemberEventContent.MembershipTypes.Join: { - if (previousMemberships.TryGetValue(membership.StateKey, out var previous) - && (previous.TypedContent as RoomMemberEventContent).Membership == RoomMemberEventContent.MembershipTypes.Join) { - if (_showUpdates) { -

@membership.Sender changed their profile

+ break; } - } - else { - if (_showJoins) { -

@membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")

+ case RoomMemberEventContent.MembershipTypes.Join: { + if (previousContent is { Membership: RoomMemberEventContent.MembershipTypes.Join }) { + if (_showUpdates) { +

+ @membership.Sender changed their profile
+ Display name: @previousContent.DisplayName -> @content.DisplayName
+ Avatar URL: @previousContent.AvatarUrl -> @content.AvatarUrl +

+ } + } + else { + if (_showJoins) { +

+ @membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")
+ Display name: @content.DisplayName
+ Avatar URL: @content.AvatarUrl +

+ } + } + + break; + } + default: { + Unknown membership @content.Membership! + break; } } - - break; - } - default: { - Unknown membership @content.Membership! - break; - } - } +
}
@@ -217,9 +234,9 @@ StateHasChanged(); } } - + private string sender = ""; - + private string Sender { get => sender; set { @@ -227,9 +244,9 @@ StateHasChanged(); } } - + private string user = ""; - + private string User { get => user; set { diff --git a/MatrixUtils.Web/Shared/MainLayout.razor.css b/MatrixUtils.Web/Shared/MainLayout.razor.css index 01a5066..924393b 100644 --- a/MatrixUtils.Web/Shared/MainLayout.razor.css +++ b/MatrixUtils.Web/Shared/MainLayout.razor.css @@ -57,6 +57,7 @@ main { .sidebar { width: 250px; + min-width: 250px; height: 100vh; position: sticky; top: 0; diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor new file mode 100644 index 0000000..11ba18a --- /dev/null +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor @@ -0,0 +1,102 @@ +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Reflection +@using ArcaneLibs.Attributes +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes +@using LibMatrix.RoomTypes + + Policy type: +
+ + Reason: +
+ + Recommendation: +
+ + Entities:
+
+ + + @*
*@ + @* JSON data *@ + @*
 *@
+    @*             $1$ @PolicyEvent.ToJson(true, true) #1# *@
+    @*     
*@ + @*
*@ + Cancel + Save + +
+ +@code { + + [Parameter] + public required Action OnClose { get; set; } + + [Parameter] + public required Action OnSaved { get; set; } + + [Parameter] + public required GenericRoom Room { get; set; } + + public string Recommendation { get; set; } = "m.ban"; + public string Reason { get; set; } = "spam"; + public string Users { get; set; } = ""; + + private static FrozenSet KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + private static Dictionary PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + + private string? MappedType { get; set; } + + private async Task Save() { + try { + await DoActualSave(); + } + catch (Exception e) { + Console.WriteLine($"Failed to save: {e}"); + } + } + + private async Task DoActualSave() { + Console.WriteLine($"Saving ---"); + Console.WriteLine($"Users = {Users}"); + var users = Users.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList(); + var tasks = users.Select(x => ExecuteBan(Room, x)).ToList(); + await Task.WhenAll(tasks); + + OnSaved.Invoke(); + } + + private async Task ExecuteBan(GenericRoom room, string entity) { + bool success = false; + while (!success) { + try { + var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent; + content.Recommendation = Recommendation; + content.Reason = Reason; + content.Entity = entity; + await room.SendStateEventAsync(MappedType!, content.GetDraupnir2StateKey(), content); + success = true; + } + catch (MatrixException e) { + if (e is not { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; + Console.WriteLine(e); + } + catch (Exception e) { + //ignored + Console.WriteLine(e); + } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor index 1bd00d1..fc536c0 100644 --- a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor @@ -35,39 +35,61 @@ @foreach (var prop in props) { + var isNullable = Nullable.GetUnderlyingType(prop.PropertyType) is not null; @prop.GetFriendlyName() - @if (Nullable.GetUnderlyingType(prop.PropertyType) is not null) { + @if (Nullable.GetUnderlyingType(prop.PropertyType) is null) { * } @{ var getter = prop.GetGetMethod(); var setter = prop.GetSetMethod(); - } - @switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { - case Type t when t == typeof(string): - {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"> - break; - default: -

Unsupported type: @prop.PropertyType

- break; + if (getter is null) { +

Missing property getter: @prop.Name

+ } + else { + switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { + case Type t when t == typeof(string): + {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"> + break; + case Type t when t == typeof(DateTime): + if (!isNullable) { + {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"> + } + else { + var value = getter?.Invoke(PolicyData, null) as DateTime?; + if (value is null) { + + } + else { + var notNullValue = Nullable.GetValueRefOrDefaultRef(ref value); + Console.WriteLine($"Value: {value?.ToString() ?? "null"}"); + {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"> + + } + } + + break; + default: +

Unsupported type: @prop.PropertyType

+ break; + } + } } } -
-
-            @PolicyEvent.ToJson(true, false)
-        
+
+ JSON data +
+                @PolicyEvent.ToJson(true, true)
+            
+
Cancel Save - @* Target entity: *@ - @*
*@ - @* Reason: *@ - @* *@ } else {

Policy data is null

@@ -102,7 +124,7 @@ .ToDictionary(x => x.GetCustomAttributes().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); private StateEventResponse? _policyEvent; - + private string? MappedType { get => _policyEvent?.Type; set { @@ -110,9 +132,9 @@ PolicyEvent.Type = value; PolicyEvent.TypedContent ??= Activator.CreateInstance(PolicyTypes[value]) as PolicyRuleEventContent; PolicyData = PolicyEvent.TypedContent as PolicyRuleEventContent; + PolicyData.Recommendation ??= "m.ban"; } } } - } \ No newline at end of file -- cgit 1.5.1