From d0d11db2209a8be65c27e15ca9d8a3b594f1a352 Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 23 Feb 2024 13:57:06 +0100 Subject: Add eons of work because I forgot to push --- .editorconfig | 13 +- .idea/.idea.MatrixRoomUtils/.idea/avalonia.xml | 1 + .../.idea/inspectionProfiles/Project_Default.xml | 22 ++ LibMatrix | 2 +- MatrixRoomUtils.sln | 12 + MatrixUtils.Abstractions/RoomInfo.cs | 48 +++- MatrixUtils.Desktop/MainWindow.axaml.cs | 4 +- MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj | 31 +++ MatrixUtils.DmSpaced/ModerationBot.cs | 298 +++++++++++++++++++++ MatrixUtils.DmSpaced/ModerationBotConfiguration.cs | 10 + MatrixUtils.DmSpaced/Program.cs | 40 +++ MatrixUtils.DmSpaced/appsettings.Development.json | 24 ++ MatrixUtils.DmSpaced/appsettings.json | 9 + MatrixUtils.LibDMSpace/DMSpaceRoom.cs | 192 ++++++++++--- MatrixUtils.LibDMSpace/HomeserverExtensions.cs | 10 + MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs | 9 +- .../StateEvents/DMSpaceChildLayer.cs | 20 ++ MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs | 2 +- MatrixUtils.Web/MatrixUtils.Web.csproj | 1 + MatrixUtils.Web/Pages/Dev/DevUtilities.razor | 6 +- MatrixUtils.Web/Pages/HSEInit.razor | 2 +- MatrixUtils.Web/Pages/Index.razor | 58 +--- .../Moderation/DraupnirProtectedRoomsEditor.razor | 10 +- .../Pages/Moderation/UserRoomHistory.razor | 4 +- MatrixUtils.Web/Pages/Rooms/Index.razor | 16 +- MatrixUtils.Web/Pages/Rooms/Index2.razor | 85 ++++++ .../MainTabComponents/MainTabSpaceItem.razor | 27 ++ .../MainTabComponents/MainTabSpaceItem.razor.css | 15 ++ .../Rooms/Index2Components/RoomsIndex2DMsTab.razor | 53 ++++ .../Index2Components/RoomsIndex2MainTab.razor | 198 ++++++++++++++ .../Index2Components/RoomsIndex2MainTab.razor.css | 0 .../RoomsIndex2SyncContainer.razor | 202 ++++++++++++++ .../Pages/Tools/PolicyListActivity.razor | 158 +++++++++++ MatrixUtils.Web/Pages/Tools/UserTrace.razor | 29 +- MatrixUtils.Web/Pages/User/DMManager.razor | 22 +- MatrixUtils.Web/Pages/User/DMSpace.razor | 55 ++-- .../Pages/User/DMSpaceStages/DMSpaceStage0.razor | 2 +- .../Pages/User/DMSpaceStages/DMSpaceStage1.razor | 149 ++++++----- .../Pages/User/DMSpaceStages/DMSpaceStage2.razor | 116 ++++---- .../Pages/User/DMSpaceStages/DMSpaceStage3.razor | 142 +++++----- MatrixUtils.Web/Pages/User/Profile.razor | 3 +- MatrixUtils.Web/Shared/ActivityGraph.razor | 148 ++++++++++ MatrixUtils.Web/Shared/ActivityGraph.razor.css | 16 ++ MatrixUtils.Web/Shared/MainLayout.razor | 4 +- MatrixUtils.Web/Shared/MxcImage.razor | 3 +- .../Shared/RoomListComponents/RoomListSpace.razor | 4 +- MatrixUtils.Web/Shared/UserListItem.razor | 6 +- scripts/deploy.sh | 2 +- 48 files changed, 1918 insertions(+), 365 deletions(-) create mode 100644 .idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml create mode 100644 MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj create mode 100644 MatrixUtils.DmSpaced/ModerationBot.cs create mode 100644 MatrixUtils.DmSpaced/ModerationBotConfiguration.cs create mode 100644 MatrixUtils.DmSpaced/Program.cs create mode 100644 MatrixUtils.DmSpaced/appsettings.Development.json create mode 100644 MatrixUtils.DmSpaced/appsettings.json create mode 100644 MatrixUtils.LibDMSpace/HomeserverExtensions.cs create mode 100644 MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css create mode 100644 MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor create mode 100644 MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor create mode 100644 MatrixUtils.Web/Shared/ActivityGraph.razor create mode 100644 MatrixUtils.Web/Shared/ActivityGraph.razor.css diff --git a/.editorconfig b/.editorconfig index e98e832..1a3cf7e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -373,7 +373,8 @@ dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:error -file_header_template = # ReSharper properties +file_header_template = # ReSharper properties + resharper_alignment_tab_fill_style = use_spaces resharper_align_first_arg_by_paren = false @@ -608,8 +609,8 @@ resharper_line_break_before_requires_clause = do_not_change resharper_linkage_specification_braces = end_of_line resharper_linkage_specification_indentation = none resharper_local_function_body = expression_body -resharper_macro_block_begin = -resharper_macro_block_end = +resharper_macro_block_begin = +resharper_macro_block_end = resharper_max_array_initializer_elements_on_line = 10000 resharper_max_attribute_length_for_same_line = 38 resharper_max_enum_members_on_line = 3 @@ -624,7 +625,7 @@ resharper_new_line_before_catch = true resharper_new_line_before_else = true resharper_new_line_before_enumerators = true resharper_normalize_tag_names = false -resharper_no_indent_inside_elements = +resharper_no_indent_inside_elements = resharper_no_indent_inside_if_element_longer_than = 2000000 resharper_null_checking_pattern_style = not_null_pattern resharper_object_creation_when_type_evident = target_typed @@ -677,7 +678,7 @@ resharper_requires_expression_braces = next_line resharper_resx_allow_far_alignment = false resharper_resx_attribute_indent = single_indent resharper_resx_insert_final_newline = false -resharper_resx_linebreak_before_elements = +resharper_resx_linebreak_before_elements = resharper_resx_max_blank_lines_between_tags = 0 resharper_resx_max_line_length = 2147483647 resharper_resx_pi_attribute_style = do_not_touch @@ -904,7 +905,7 @@ resharper_xmldoc_wrap_text = true resharper_xml_allow_far_alignment = false resharper_xml_attribute_indent = align_by_first_attribute resharper_xml_insert_final_newline = false -resharper_xml_linebreak_before_elements = +resharper_xml_linebreak_before_elements = resharper_xml_max_blank_lines_between_tags = 2 resharper_xml_max_line_length = 180 resharper_xml_pi_attribute_style = do_not_touch diff --git a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml index f74ab1c..0aa65bb 100644 --- a/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml +++ b/.idea/.idea.MatrixRoomUtils/.idea/avalonia.xml @@ -12,6 +12,7 @@ + diff --git a/.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml b/.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..55540ea --- /dev/null +++ b/.idea/.idea.MatrixRoomUtils/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/LibMatrix b/LibMatrix index 3dfb7b8..c7b7dbe 160000 --- a/LibMatrix +++ b/LibMatrix @@ -1 +1 @@ -Subproject commit 3dfb7b81b0fe19d37a7bf1183e248ca10c56277c +Subproject commit c7b7dbe3d929d787fe0c76015082a117c4222278 diff --git a/MatrixRoomUtils.sln b/MatrixRoomUtils.sln index 9cccf09..924697b 100644 --- a/MatrixRoomUtils.sln +++ b/MatrixRoomUtils.sln @@ -55,6 +55,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.DevTestBot", "LibMatrix\Utilities\LibMatrix.DevTestBot\LibMatrix.DevTestBot.csproj", "{5CE239F8-C124-4A96-A0F8-B56B9AE27434}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibMatrix.HomeserverEmulator", "LibMatrix\Tests\LibMatrix.HomeserverEmulator\LibMatrix.HomeserverEmulator.csproj", "{6D93DA72-69D8-43BD-BC19-7FFF8A313971}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArcaneLibs.UsageTest", "LibMatrix\ArcaneLibs\ArcaneLibs.UsageTest\ArcaneLibs.UsageTest.csproj", "{EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.DmSpaced", "MatrixUtils.DmSpaced\MatrixUtils.DmSpaced.csproj", "{B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -150,6 +153,14 @@ Global {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Debug|Any CPU.Build.0 = Debug|Any CPU {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Release|Any CPU.ActiveCfg = Release|Any CPU {6D93DA72-69D8-43BD-BC19-7FFF8A313971}.Release|Any CPU.Build.0 = Release|Any CPU + {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0}.Release|Any CPU.Build.0 = Release|Any CPU + {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3FEA1EF-6CFE-49C5-A0B2-11DB58D4CD1C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F4E241C3-0300-4B87-8707-BCBDEF1F0185} = {8F4F6BEC-0C66-486B-A21A-1C35B2EDAD33} @@ -172,5 +183,6 @@ Global {CC836863-0EE8-44BD-BF39-45076F57C416} = {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} {5CE239F8-C124-4A96-A0F8-B56B9AE27434} = {A4BCBF5F-4936-44B9-BAB3-FAF240BDF40D} {6D93DA72-69D8-43BD-BC19-7FFF8A313971} = {7D2C9959-8309-4110-A67F-DEE64E97C1D8} + {EDFC9BA8-CAA9-4B51-AFAF-834C1D74DCF0} = {B00C5CB6-6200-4B41-96BE-C6EAF1085A14} EndGlobalSection EndGlobal diff --git a/MatrixUtils.Abstractions/RoomInfo.cs b/MatrixUtils.Abstractions/RoomInfo.cs index 877246b..53acbee 100644 --- a/MatrixUtils.Abstractions/RoomInfo.cs +++ b/MatrixUtils.Abstractions/RoomInfo.cs @@ -11,16 +11,17 @@ using LibMatrix.RoomTypes; namespace MatrixUtils.Abstractions; public class RoomInfo : NotifyPropertyChanged { - public required GenericRoom Room { get; set; } - public ObservableCollection StateEvents { get; } = new(); + public readonly GenericRoom Room; + public ObservableCollection StateEvents { get; private set; } = new(); private static ConcurrentBag homeserversWithoutEventFormatSupport = new(); - + private static SvgIdenticonGenerator identiconGenerator = new(); + public async Task GetStateEvent(string type, string stateKey = "") { if (homeserversWithoutEventFormatSupport.Contains(Room.Homeserver)) return await GetStateEventForged(type, stateKey); var @event = StateEvents.FirstOrDefault(x => x?.Type == type && x.StateKey == stateKey); if (@event is not null) return @event; - + try { @event = await Room.GetStateEventOrNullAsync(type, stateKey); StateEvents.Add(@event); @@ -30,6 +31,7 @@ public class RoomInfo : NotifyPropertyChanged { homeserversWithoutEventFormatSupport.Add(Room.Homeserver); return await GetStateEventForged(type, stateKey); } + Console.Error.WriteLine(e); await Task.Delay(1000); return await GetStateEvent(type, stateKey); @@ -79,7 +81,7 @@ public class RoomInfo : NotifyPropertyChanged { } public string? RoomIcon { - get => _roomIcon ?? "https://api.dicebear.com/6.x/identicon/svg?seed=" + Room.RoomId; + get => _roomIcon ?? _fallbackIcon; set => SetField(ref _roomIcon, value); } @@ -93,18 +95,17 @@ public class RoomInfo : NotifyPropertyChanged { set => SetField(ref _creationEventContent, value); } + private string? _roomCreator; + public string? RoomCreator { get => _roomCreator; set => SetField(ref _roomCreator, value); } - // public string? GetRoomIcon() => (StateEvents.FirstOrDefault(x => x?.Type == RoomAvatarEventContent.EventId)?.TypedContent as RoomAvatarEventContent)?.Url ?? - // "mxc://rory.gay/dgP0YPjJEWaBwzhnbyLLwGGv"; - private string? _roomIcon; + private readonly string _fallbackIcon; private string? _roomName; private RoomCreateEventContent? _creationEventContent; - private string? _roomCreator; private string? _overrideRoomType; private string? _defaultRoomName; private RoomMemberEventContent? _ownMembership; @@ -130,11 +131,25 @@ public class RoomInfo : NotifyPropertyChanged { set => SetField(ref _ownMembership, value); } - public RoomInfo() { + public RoomInfo(GenericRoom room) { + Room = room; + _fallbackIcon = identiconGenerator.GenerateAsDataUri(room.RoomId); + registerEventListener(); + } + + public RoomInfo(GenericRoom room, List? stateEvents) { + Room = room; + _fallbackIcon = identiconGenerator.GenerateAsDataUri(room.RoomId); + if (stateEvents is { Count: > 0 }) StateEvents = new(stateEvents!); + registerEventListener(); + } + + private void registerEventListener() { StateEvents.CollectionChanged += (_, args) => { if (args.NewItems is { Count: > 0 }) - foreach (StateEventResponse? newState in args.NewItems) { // TODO: switch statement benchmark? - if(newState is null) continue; + foreach (StateEventResponse? newState in args.NewItems) { + // TODO: switch statement benchmark? + if (newState is null) continue; if (newState.Type == RoomNameEventContent.EventId && newState.TypedContent is RoomNameEventContent roomNameContent) RoomName = roomNameContent.Name; else if (newState is { Type: RoomAvatarEventContent.EventId, TypedContent: RoomAvatarEventContent roomAvatarContent }) @@ -146,4 +161,11 @@ public class RoomInfo : NotifyPropertyChanged { } }; } -} + + public async Task FetchAllStateAsync() { + var stateEvents = Room.GetFullStateAsync(); + await foreach (var stateEvent in stateEvents) { + StateEvents.Add(stateEvent); + } + } +} \ No newline at end of file diff --git a/MatrixUtils.Desktop/MainWindow.axaml.cs b/MatrixUtils.Desktop/MainWindow.axaml.cs index 562ab1a..9c783e4 100644 --- a/MatrixUtils.Desktop/MainWindow.axaml.cs +++ b/MatrixUtils.Desktop/MainWindow.axaml.cs @@ -42,9 +42,7 @@ public partial class MainWindow : Window { // roomList.Children.Add(new RoomListEntry(_scopeFactory, new RoomInfo(room))); windowContent.Push("home", new RoomListEntry() { - Room = new RoomInfo() { - Room = room - } + Room = new RoomInfo(room) }); base.OnLoaded(e); } diff --git a/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj b/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj new file mode 100644 index 0000000..4b0f599 --- /dev/null +++ b/MatrixUtils.DmSpaced/MatrixUtils.DmSpaced.csproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + preview + enable + enable + false + true + + + + + + + + + + + + + + + + + + Always + + + diff --git a/MatrixUtils.DmSpaced/ModerationBot.cs b/MatrixUtils.DmSpaced/ModerationBot.cs new file mode 100644 index 0000000..6e534fc --- /dev/null +++ b/MatrixUtils.DmSpaced/ModerationBot.cs @@ -0,0 +1,298 @@ +using ArcaneLibs.Extensions; +using LibMatrix; +using LibMatrix.EventTypes.Spec; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.Policy; +using LibMatrix.Helpers; +using LibMatrix.Homeservers; +using LibMatrix.RoomTypes; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ModerationBot; + +public class ModerationBot(AuthenticatedHomeserverGeneric hs, ILogger logger, ModerationBotConfiguration configuration, PolicyEngine engine) : IHostedService { + private Task _listenerTask; + + // private GenericRoom _policyRoom; + private GenericRoom? _logRoom; + private GenericRoom? _controlRoom; + + /// Triggered when the application host is ready to start the service. + /// Indicates that the start process has been aborted. + public async Task StartAsync(CancellationToken cancellationToken) { + _listenerTask = Run(cancellationToken); + logger.LogInformation("Bot started!"); + } + + private async Task Run(CancellationToken cancellationToken) { + if (Directory.Exists("bot_data/cache")) + Directory.GetFiles("bot_data/cache").ToList().ForEach(File.Delete); + + BotData botData; + + try { + logger.LogInformation("Fetching bot account data..."); + botData = await hs.GetAccountDataAsync("gay.rory.moderation_bot_data"); + logger.LogInformation("Got bot account data..."); + } + catch (Exception e) { + logger.LogInformation("Could not fetch bot account data... {}", e.Message); + if (e is not MatrixException { ErrorCode: "M_NOT_FOUND" }) { + logger.LogError("{}", e.ToString()); + throw; + } + + botData = null; + } + + botData = await FirstRunTasks.ConstructBotData(hs, configuration, botData); + + // _policyRoom = hs.GetRoom(botData.PolicyRoom ?? botData.ControlRoom); + _logRoom = hs.GetRoom(botData.LogRoom ?? botData.ControlRoom); + _controlRoom = hs.GetRoom(botData.ControlRoom); + foreach (var configurationAdmin in configuration.Admins) { + var pls = await _controlRoom.GetPowerLevelsAsync(); + if (pls is null) { + await _logRoom?.SendMessageEventAsync(MessageFormatter.FormatWarning($"Control room has no m.room.power_levels?")); + continue; + } + pls.SetUserPowerLevel(configurationAdmin, pls.GetUserPowerLevel(hs.UserId)); + await _controlRoom.SendStateEventAsync(RoomPowerLevelEventContent.EventId, pls); + } + var syncHelper = new SyncHelper(hs); + + List admins = new(); + +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Task.Run(async () => { + while (!cancellationToken.IsCancellationRequested) { + var controlRoomMembers = _controlRoom.GetMembersEnumerableAsync(); + var pls = await _controlRoom.GetPowerLevelsAsync(); + await foreach (var member in controlRoomMembers) { + if ((member.TypedContent as RoomMemberEventContent)? + .Membership == "join" && pls.UserHasTimelinePermission(member.Sender, RoomMessageEventContent.EventId)) admins.Add(member.StateKey); + } + + await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken); + } + }, cancellationToken); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + + syncHelper.InviteReceivedHandlers.Add(async Task (args) => { + var inviteEvent = + args.Value.InviteState.Events.FirstOrDefault(x => + x.Type == "m.room.member" && x.StateKey == hs.UserId); + logger.LogInformation("Got invite to {RoomId} by {Sender} with reason: {Reason}", args.Key, inviteEvent!.Sender, + (inviteEvent.TypedContent as RoomMemberEventContent)!.Reason); + await _logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccess($"Bot invited to {MessageFormatter.HtmlFormatMention(args.Key)} by {MessageFormatter.HtmlFormatMention(inviteEvent.Sender)}")); + if (admins.Contains(inviteEvent.Sender)) { + try { + await _logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccess($"Joining {MessageFormatter.HtmlFormatMention(args.Key)}...")); + var senderProfile = await hs.GetProfileAsync(inviteEvent.Sender); + await hs.GetRoom(args.Key).JoinAsync(reason: $"I was invited by {senderProfile.DisplayName ?? inviteEvent.Sender}!"); + } + catch (Exception e) { + logger.LogError("{}", e.ToString()); + await _logRoom.SendMessageEventAsync(MessageFormatter.FormatException("Could not join room", e)); + await hs.GetRoom(args.Key).LeaveAsync(reason: "I was unable to join the room: " + e); + } + } + }); + + syncHelper.TimelineEventHandlers.Add(async @event => { + var room = hs.GetRoom(@event.RoomId); + try { + logger.LogInformation( + "Got timeline event in {}: {}", @event.RoomId, @event.ToJson(indent: true, ignoreNull: true)); + + if (@event != null && ( + @event.MappedType.IsAssignableTo(typeof(BasePolicy)) + || @event.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent)) + )) { + await LogPolicyChange(@event); + await engine.ReloadActivePolicyListById(@event.RoomId); + } + + var rules = await engine.GetMatchingPolicies(@event); + foreach (var matchedRule in rules) { + await _logRoom.SendMessageEventAsync(MessageFormatter.FormatSuccessJson( + $"{MessageFormatter.HtmlFormatMessageLink(eventId: @event.EventId, roomId: room.RoomId, displayName: "Event")} matched {MessageFormatter.HtmlFormatMessageLink(eventId: @matchedRule.OriginalEvent.EventId, roomId: matchedRule.PolicyList.Room.RoomId, displayName: "rule")}", @matchedRule.OriginalEvent.RawContent)); + } + + if (configuration.DemoMode) { + // foreach (var matchedRule in rules) { + // await room.SendMessageEventAsync(MessageFormatter.FormatSuccessJson( + // $"{MessageFormatter.HtmlFormatMessageLink(eventId: @event.EventId, roomId: room.RoomId, displayName: "Event")} matched {MessageFormatter.HtmlFormatMessageLink(eventId: @matchedRule.EventId, roomId: matchedRule.RoomId, displayName: "rule")}", @matchedRule.RawContent)); + // } + return; + } + // + // if (@event is { Type: "m.room.message", TypedContent: RoomMessageEventContent message }) { + // if (message is { MessageType: "m.image" }) { + // //check media + // // var matchedPolicy = await CheckMedia(@event); + // var matchedPolicy = rules.FirstOrDefault(); + // if (matchedPolicy is null) return; + // var matchedpolicyData = matchedPolicy.TypedContent as MediaPolicyEventContent; + // await _logRoom.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: + // $"User {MessageFormatter.HtmlFormatMention(@event.Sender)} posted an image in {MessageFormatter.HtmlFormatMention(room.RoomId)} that matched rule {matchedPolicy.StateKey}, applying action {matchedpolicyData.Recommendation}, as described in rule: {matchedPolicy.RawContent!.ToJson(ignoreNull: true)}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = + // $"User {MessageFormatter.HtmlFormatMention(@event.Sender)} posted an image in {MessageFormatter.HtmlFormatMention(room.RoomId)} that matched rule {matchedPolicy.StateKey}, applying action {matchedpolicyData.Recommendation}, as described in rule:
{matchedPolicy.RawContent!.ToJson(ignoreNull: true)}
" + // }); + // switch (matchedpolicyData.Recommendation) { + // case "warn_admins": { + // await _controlRoom.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: $"{string.Join(' ', admins)}\nUser {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image {message.Url}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = $"{string.Join(' ', admins.Select(u => MessageFormatter.HtmlFormatMention(u)))}\n" + + // $"User {MessageFormatter.HtmlFormatMention(@event.Sender)} posted a banned image {message.Url}" + // }); + // break; + // } + // case "warn": { + // await room.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: $"Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = + // $"Please be careful when posting this image: {matchedpolicyData.Reason ?? "No reason specified"}" + // }); + // break; + // } + // case "redact": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason ?? "No reason specified"); + // break; + // } + // case "spoiler": { + // //
+ // // @emma:rory.gay
+ // // + // // + // // CN + // // : + // // test
+ // // + // // + // // + // //
+ // await room.SendMessageEventAsync( + // new RoomMessageEventContent( + // body: + // $"Please be careful when posting this image: {matchedpolicyData.Reason}, I have spoilered it for you:", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = + // $"Please be careful when posting this image: {matchedpolicyData.Reason}, I have spoilered it for you:" + // }); + // var imageUrl = message.Url; + // await room.SendMessageEventAsync( + // new RoomMessageEventContent(body: $"CN: {imageUrl}", + // messageType: "m.text") { + // Format = "org.matrix.custom.html", + // FormattedBody = $""" + //
+ // + // CN + // : + // {matchedpolicyData.Reason}
+ // + // + // + // + // + //
+ // """ + // }); + // await room.RedactEventAsync(@event.EventId, "Automatically spoilered: " + matchedpolicyData.Reason); + // break; + // } + // case "mute": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); + // //change powerlevel to -1 + // var currentPls = await room.GetPowerLevelsAsync(); + // if (currentPls is null) { + // logger.LogWarning("Unable to get power levels for {room}", room.RoomId); + // await _logRoom.SendMessageEventAsync( + // MessageFormatter.FormatError($"Unable to get power levels for {MessageFormatter.HtmlFormatMention(room.RoomId)}")); + // return; + // } + // + // currentPls.Users ??= new(); + // currentPls.Users[@event.Sender] = -1; + // await room.SendStateEventAsync("m.room.power_levels", currentPls); + // break; + // } + // case "kick": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); + // await room.KickAsync(@event.Sender, matchedpolicyData.Reason); + // break; + // } + // case "ban": { + // await room.RedactEventAsync(@event.EventId, matchedpolicyData.Reason); + // await room.BanAsync(@event.Sender, matchedpolicyData.Reason); + // break; + // } + // default: { + // throw new ArgumentOutOfRangeException("recommendation", + // $"Unknown response type {matchedpolicyData.Recommendation}!"); + // } + // } + // } + // } + } + catch (Exception e) { + logger.LogError("{}", e.ToString()); + await _controlRoom.SendMessageEventAsync( + MessageFormatter.FormatException($"Unable to process event in {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); + await _logRoom.SendMessageEventAsync( + MessageFormatter.FormatException($"Unable to process event in {MessageFormatter.HtmlFormatMention(room.RoomId)}", e)); + await using var stream = new MemoryStream(e.ToString().AsBytes().ToArray()); + await _controlRoom.SendFileAsync("error.log.cs", stream); + await _logRoom.SendFileAsync("error.log.cs", stream); + } + }); + await engine.ReloadActivePolicyLists(); + await syncHelper.RunSyncLoopAsync(); + } + + private async Task LogPolicyChange(StateEventResponse changeEvent) { + var room = hs.GetRoom(changeEvent.RoomId!); + var message = MessageFormatter.FormatWarning($"Policy change detected in {MessageFormatter.HtmlFormatMessageLink(changeEvent.RoomId, changeEvent.EventId, [hs.ServerName], await room.GetNameOrFallbackAsync())}!"); + message = message.ConcatLine(new RoomMessageEventContent(body: $"Policy type: {changeEvent.Type} -> {changeEvent.MappedType.Name}") { + FormattedBody = $"Policy type: {changeEvent.Type} -> {changeEvent.MappedType.Name}" + }); + var isUpdated = changeEvent.Unsigned.PrevContent is { Count: > 0 }; + var isRemoved = changeEvent.RawContent is not { Count: > 0 }; + // if (isUpdated) { + // message = message.ConcatLine(MessageFormatter.FormatSuccess("Rule updated!")); + // message = message.ConcatLine(MessageFormatter.FormatSuccessJson("Old rule:", changeEvent.Unsigned.PrevContent!)); + // } + // else if (isRemoved) { + // message = message.ConcatLine(MessageFormatter.FormatWarningJson("Rule removed!", changeEvent.Unsigned.PrevContent!)); + // } + // else { + // message = message.ConcatLine(MessageFormatter.FormatSuccess("New rule added!")); + // } + message = message.ConcatLine(MessageFormatter.FormatSuccessJson($"{(isUpdated ? "Updated" : isRemoved ? "Removed" : "New")} rule: {changeEvent.StateKey}", changeEvent.RawContent!)); + if (isRemoved || isUpdated) { + message = message.ConcatLine(MessageFormatter.FormatSuccessJson("Old content: ", changeEvent.Unsigned.PrevContent!)); + } + + await _logRoom.SendMessageEventAsync(message); + } + + /// Triggered when the application host is performing a graceful shutdown. + /// Indicates that the shutdown process should no longer be graceful. + public async Task StopAsync(CancellationToken cancellationToken) { + logger.LogInformation("Shutting down bot!"); + } + +} diff --git a/MatrixUtils.DmSpaced/ModerationBotConfiguration.cs b/MatrixUtils.DmSpaced/ModerationBotConfiguration.cs new file mode 100644 index 0000000..47e6423 --- /dev/null +++ b/MatrixUtils.DmSpaced/ModerationBotConfiguration.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Configuration; + +namespace ModerationBot; + +public class ModerationBotConfiguration { + public ModerationBotConfiguration(IConfiguration config) => config.GetRequiredSection("ModerationBot").Bind(this); + + public string Homeserver { get; set; } + public string AccessToken { get; set; } +} diff --git a/MatrixUtils.DmSpaced/Program.cs b/MatrixUtils.DmSpaced/Program.cs new file mode 100644 index 0000000..ae352b7 --- /dev/null +++ b/MatrixUtils.DmSpaced/Program.cs @@ -0,0 +1,40 @@ +// See https://aka.ms/new-console-template for more information + +using LibMatrix.Services; +using LibMatrix.Utilities.Bot; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using ModerationBot; +using ModerationBot.Services; + +Console.WriteLine("Hello, World!"); + +var builder = Host.CreateDefaultBuilder(args); + +builder.ConfigureHostOptions(host => { + host.ServicesStartConcurrently = true; + host.ServicesStopConcurrently = true; + host.ShutdownTimeout = TimeSpan.FromSeconds(5); +}); + +if (Environment.GetEnvironmentVariable("MODERATIONBOT_APPSETTINGS_PATH") is string path) + builder.ConfigureAppConfiguration(x => x.AddJsonFile(path)); + +var host = builder.ConfigureServices((_, services) => { + services.AddScoped(x => + new TieredStorageService( + cacheStorageProvider: new FileStorageProvider("bot_data/cache/"), + dataStorageProvider: new FileStorageProvider("bot_data/data/") + ) + ); + services.AddSingleton(); + + services.AddRoryLibMatrixServices(); + + services.AddSingleton(); + + services.AddHostedService(); +}).UseConsoleLifetime().Build(); + +await host.RunAsync(); \ No newline at end of file diff --git a/MatrixUtils.DmSpaced/appsettings.Development.json b/MatrixUtils.DmSpaced/appsettings.Development.json new file mode 100644 index 0000000..224d0da --- /dev/null +++ b/MatrixUtils.DmSpaced/appsettings.Development.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "LibMatrixBot": { + // The homeserver to connect to + "Homeserver": "rory.gay", + // The access token to use + "AccessToken": "syt_xxxxxxxxxxxxxxxxx", + // The command prefix + "Prefix": "?" + }, + "ModerationBot": { + // List of people who should be invited to the control room + "Admins": [ + "@emma:conduit.rory.gay", + "@emma:rory.gay" + ] + } +} diff --git a/MatrixUtils.DmSpaced/appsettings.json b/MatrixUtils.DmSpaced/appsettings.json new file mode 100644 index 0000000..6ba02f3 --- /dev/null +++ b/MatrixUtils.DmSpaced/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/MatrixUtils.LibDMSpace/DMSpaceRoom.cs b/MatrixUtils.LibDMSpace/DMSpaceRoom.cs index 3fb0ab6..e2c8192 100644 --- a/MatrixUtils.LibDMSpace/DMSpaceRoom.cs +++ b/MatrixUtils.LibDMSpace/DMSpaceRoom.cs @@ -1,6 +1,10 @@ +using System.Net; using ArcaneLibs.Extensions; using LibMatrix; +using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; +using LibMatrix.Responses; using LibMatrix.RoomTypes; using MatrixUtils.LibDMSpace.StateEvents; @@ -9,75 +13,179 @@ namespace MatrixUtils.LibDMSpace; public class DMSpaceRoom(AuthenticatedHomeserverGeneric homeserver, string roomId) : SpaceRoom(homeserver, roomId) { private readonly GenericRoom _room; - public async Task GetDmSpaceInfo() { + // ReSharper disable once InconsistentNaming + public async Task GetDMSpaceInfo() { return await GetStateOrNullAsync(DMSpaceInfo.EventId); } - public async IAsyncEnumerable GetChildrenAsync(bool includeRemoved = false) { - var rooms = new List(); + public async Task>?> ExportNativeDMs() { var state = GetFullStateAsync(); - await foreach (var stateEvent in state) { - if (stateEvent!.Type != "m.space.child") continue; - if (stateEvent.RawContent!.ToJson() != "{}" || includeRemoved) - yield return homeserver.GetRoom(stateEvent.StateKey); - } - } - - public async Task AddChildAsync(GenericRoom room) { - var members = room.GetMembersEnumerableAsync(true); - Dictionary memberCountByHs = new(); - await foreach (var member in members) { - var server = member.StateKey.Split(':')[1]; - if (memberCountByHs.ContainsKey(server)) memberCountByHs[server]++; - else memberCountByHs[server] = 1; - } + var mdirect = new Dictionary>(); + await foreach (var stateEvent in state) { } - var resp = await SendStateEventAsync("m.space.child", room.RoomId, new { - via = memberCountByHs - .OrderByDescending(x => x.Value) - .Select(x => x.Key) - .Take(10) - }); - return resp; + return mdirect; } public async Task ImportNativeDMs() { - var dmSpaceInfo = await GetDmSpaceInfo(); + var dmSpaceInfo = await GetDMSpaceInfo(); if (dmSpaceInfo is null) throw new NullReferenceException("DM Space is not configured!"); if (dmSpaceInfo.LayerByUser) await ImportNativeDMsIntoLayers(); else await ImportNativeDMsWithoutLayers(); } - #region Import Native DMs + public async Task> GetAllActiveLayersAsync() { + var state = await GetFullStateAsListAsync(); + return state.Where(x => x.Type == DMSpaceChildLayer.EventId && x.RawContent.ContainsKey("space_id")).ToList(); + } + +#region Import Native DMs private async Task ImportNativeDMsWithoutLayers() { var mdirect = await homeserver.GetAccountDataAsync>>("m.direct"); foreach (var (userId, dmRooms) in mdirect) { foreach (var roomid in dmRooms) { var dri = new DMRoomInfo() { - RemoteUsers = new() { - userId - } + AttributedUser = userId }; - // Add all DM room members - var members = homeserver.GetRoom(roomid).GetMembersEnumerableAsync(); - await foreach (var member in members) - if (member.StateKey != userId) - dri.RemoteUsers.Add(member.StateKey); - // Remove members of DM space - members = GetMembersEnumerableAsync(); - await foreach (var member in members) - if (dri.RemoteUsers.Contains(member.StateKey)) - dri.RemoteUsers.Remove(member.StateKey); await SendStateEventAsync(DMRoomInfo.EventId, roomid, dri); + await AddChildAsync(Homeserver.GetRoom(roomid)); } } } private async Task ImportNativeDMsIntoLayers() { var mdirect = await homeserver.GetAccountDataAsync>>("m.direct"); + var layerTasks = mdirect.Select(async entry => { + var (userId, dmRooms) = entry; + DMSpaceChildLayer? layer = await GetStateOrNullAsync(DMSpaceChildLayer.EventId, userId.UrlEncode()) ?? await CreateLayer(userId); + return (entry, layer); + }).ToAsyncEnumerable(); + + await foreach (var ((userId, dmRooms), layer) in layerTasks) { + var space = Homeserver.GetRoom(layer.SpaceId).AsSpace; + foreach (var roomid in dmRooms) { + var dri = new DMRoomInfo() { + AttributedUser = userId + }; + await space.SendStateEventAsync(DMRoomInfo.EventId, roomid, dri); + await space.AddChildAsync(Homeserver.GetRoom(roomid)); + } + + await UpdateLayer(layer, userId); + } + + // ensure all spaces are linked + Console.WriteLine("Ensuring all child layers are inside space..."); + var layerAssuranceTasks = (await GetFullStateAsListAsync()) + .Where(x => x.Type == DMSpaceChildLayer.EventId && (x.RawContent?.ContainsKey("space_id") ?? false)) + .Select(async layer => { + var content = layer.TypedContent as DMSpaceChildLayer; + var space = homeserver.GetRoom(content!.SpaceId); + try { + var state = await space.GetFullStateAsListAsync(); + if (!state.Any(e => e.Type == DMRoomInfo.EventId)) throw new Exception(); + } + catch { + await homeserver.JoinRoomAsync(content!.SpaceId); + } + + return AddChildAsync(space); + }); + await Task.WhenAll(layerAssuranceTasks); + Console.WriteLine("All child layers should be inside of space, if not, something went horribly wrong!"); + } + + private async Task CreateLayer(string userId) { + var childCreateRequest = CreateRoomRequest.CreatePrivate(homeserver, userId); + childCreateRequest.CreationContent["type"] = "m.space"; + + var layer = new DMSpaceChildLayer() { + SpaceId = (await homeserver.CreateRoom(childCreateRequest)).RoomId + }; + + await SendStateEventAsync(DMSpaceChildLayer.EventId, userId[1..], layer); + await AddChildAsync(Homeserver.GetRoom(layer.SpaceId)); + return layer; + } + + private async Task UpdateLayerProfilesAsync() { + var layers = await GetAllActiveLayersAsync(); + var getProfileTasks = layers.Select(async x => { + UserProfileResponse? profile = null; + try { + return (x, profile); + } + catch { + return (x, null); + } + + }).ToAsyncEnumerable(); + await foreach (var (layer, profile) in getProfileTasks) { + if (profile is null) continue; + var layerContent = layer.TypedContent as DMSpaceChildLayer; + var space = Homeserver.GetRoom(layerContent!.SpaceId).AsSpace; + + try { + await space.SendStateEventAsync(RoomAvatarEventContent.EventId, "", new RoomAvatarEventContent() { + Url = layerContent.OverrideAvatar ?? profile?.AvatarUrl + }); + await space.SendStateEventAsync(RoomNameEventContent.EventId, "", new RoomNameEventContent() { + Name = layerContent.OverrideName ?? profile?.DisplayName ?? "@" + layer.StateKey.UrlDecode() + }); + } + catch (MatrixException e) { + Console.WriteLine("Failed to update space: {0}", e); + } + } + } + + private async Task UpdateLayer(DMSpaceChildLayer layer, string mxid) { + UserProfileResponse? profile = null; + var space = Homeserver.GetRoom(layer.SpaceId).AsSpace; + + if (string.IsNullOrWhiteSpace(layer.OverrideAvatar) || string.IsNullOrWhiteSpace(layer.OverrideName)) { + try { + profile = await homeserver.GetProfileAsync(mxid); + } + catch (MatrixException e) { + // if (e.ErrorCode != "M_NOT_FOUND") throw; + Console.Error.WriteLine(e); + } + } + + try { + await space.SendStateEventAsync(RoomAvatarEventContent.EventId, "", new RoomAvatarEventContent() { + Url = layer.OverrideAvatar ?? profile?.AvatarUrl + }); + await space.SendStateEventAsync(RoomNameEventContent.EventId, "", new RoomNameEventContent() { + Name = layer.OverrideName ?? profile?.DisplayName ?? mxid + }); + } + catch (MatrixException e) { + Console.WriteLine("Failed to update space: {0}", e); + } + } + + public async Task DisbandDMSpace() { + var state = await GetFullStateAsListAsync(); + var leaveTasks = state.Select(async x => { + if (x.Type != DMSpaceChildLayer.EventId) return; + var content = x.TypedContent as DMSpaceChildLayer; + if (content?.SpaceId is null) return; + var space = homeserver.GetRoom(content.SpaceId); + try { + await space.LeaveAsync(); + } + catch { + // might not be in room, doesnt matter + } + }); + + await LeaveAsync(); + + await Task.WhenAll(leaveTasks); } - #endregion -} +#endregion +} \ No newline at end of file diff --git a/MatrixUtils.LibDMSpace/HomeserverExtensions.cs b/MatrixUtils.LibDMSpace/HomeserverExtensions.cs new file mode 100644 index 0000000..2bf5eaa --- /dev/null +++ b/MatrixUtils.LibDMSpace/HomeserverExtensions.cs @@ -0,0 +1,10 @@ +using LibMatrix.Homeservers; + +namespace MatrixUtils.LibDMSpace; + +public static class HomeserverExtensions { + public static async Task GetDMSpaceRoomAsync(this AuthenticatedHomeserverGeneric homeserver) { + return null; //TODO: implement + } + +} \ No newline at end of file diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs b/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs index 5aa62d7..bc595b5 100644 --- a/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs +++ b/MatrixUtils.LibDMSpace/StateEvents/DMRoomInfo.cs @@ -5,10 +5,13 @@ using LibMatrix.Interfaces; namespace MatrixUtils.LibDMSpace.StateEvents; [MatrixEvent(EventName = EventId)] -public class DMRoomInfo : TimelineEventContent { +public class DMRoomInfo : EventContent { public const string EventId = "gay.rory.dm_room_info"; - [JsonPropertyName("remote_users")] - public List RemoteUsers { get; set; } + // [JsonPropertyName("remote_users")] + // public List RemoteUsers { get; set; } + [JsonPropertyName("attributed_user")] + public string AttributedUser { get; set; } + } diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs new file mode 100644 index 0000000..16c7b70 --- /dev/null +++ b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceChildLayer.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; +using LibMatrix.EventTypes; +using LibMatrix.Interfaces; + +namespace MatrixUtils.LibDMSpace.StateEvents; + +[MatrixEvent(EventName = EventId)] +public class DMSpaceChildLayer : EventContent { + public const string EventId = "gay.rory.dm_space_child_layer"; + + [JsonPropertyName("space_id")] + public string SpaceId { get; set; } + + [JsonPropertyName("override_name")] + public string? OverrideName { get; set; } + + [JsonPropertyName("override_avatar")] + public string? OverrideAvatar { get; set; } + +} diff --git a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs index 0df1913..f5daa74 100644 --- a/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs +++ b/MatrixUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs @@ -5,7 +5,7 @@ using LibMatrix.Interfaces; namespace MatrixUtils.LibDMSpace.StateEvents; [MatrixEvent(EventName = EventId)] -public class DMSpaceInfo : TimelineEventContent { +public class DMSpaceInfo : EventContent { public const string EventId = "gay.rory.dm_space_info"; [JsonPropertyName("is_layered")] diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj index 515b235..dfb4713 100644 --- a/MatrixUtils.Web/MatrixUtils.Web.csproj +++ b/MatrixUtils.Web/MatrixUtils.Web.csproj @@ -11,6 +11,7 @@ true false service-worker-assets.js + false diff --git a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor index 611d4c1..87416a2 100644 --- a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor +++ b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor @@ -12,9 +12,9 @@ else {
Room List - @foreach (var room in Rooms) { - - + @foreach (var roomId in Rooms) { + + }
diff --git a/MatrixUtils.Web/Pages/HSEInit.razor b/MatrixUtils.Web/Pages/HSEInit.razor index 3020ff7..b2fc0db 100644 --- a/MatrixUtils.Web/Pages/HSEInit.razor +++ b/MatrixUtils.Web/Pages/HSEInit.razor @@ -6,7 +6,7 @@ @code { protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - var tasks = Enumerable.Range(0, 5000).Select(i => Login()).ToList(); + var tasks = Enumerable.Range(0, 50).Select(i => Login()).ToList(); await Task.WhenAll(tasks); Console.WriteLine("All logins complete!"); var userAuths = tasks.Select(t => t.Result).Where(t => t != null).ToList(); diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor index 0c0c87a..f216488 100644 --- a/MatrixUtils.Web/Pages/Index.razor +++ b/MatrixUtils.Web/Pages/Index.razor @@ -3,6 +3,8 @@ @using LibMatrix.Responses @using LibMatrix @using ArcaneLibs.Extensions +@using ArcaneLibs +@using System.Diagnostics Index @@ -12,7 +14,10 @@ Small collection of tools to do not-so-everyday things.

@totalSessions signed in sessions - Add new account
@if (scannedSessions != totalSessions) { - + + @scannedSessions/@totalSessions + + }
@@ -103,6 +108,7 @@ Small collection of tools to do not-so-everyday things. private readonly List _offlineSessions = []; private LoginResponse? _currentSession; int scannedSessions = 0, totalSessions = 1; + private SvgIdenticonGenerator _identiconGenerator = new(); protected override async Task OnInitializedAsync() { Console.WriteLine("Index.OnInitializedAsync"); @@ -124,6 +130,7 @@ Small collection of tools to do not-so-everyday things. List offlineServers = []; var sema = new SemaphoreSlim(64, 64); + var updateSw = Stopwatch.StartNew(); var tasks = tokens.Select(async token => { await sema.WaitAsync(); scannedSessions++; @@ -141,7 +148,7 @@ Small collection of tools to do not-so-everyday things. var serverVersionTask = hs.FederationClient?.GetServerVersionAsync(); _sessions.Add(new() { UserInfo = new() { - AvatarUrl = "/blobfox_outage.gif", + AvatarUrl = string.IsNullOrWhiteSpace((await profileTask).AvatarUrl) ? _identiconGenerator.GenerateAsDataUri(hs.WhoAmI.UserId) : hs.ResolveMediaUri((await profileTask).AvatarUrl), RoomCount = (await joinedRoomsTask).Count, DisplayName = (await profileTask).DisplayName ?? hs.WhoAmI.UserId }, @@ -149,6 +156,10 @@ Small collection of tools to do not-so-everyday things. ServerVersion = await (serverVersionTask ?? Task.FromResult(null)!), Homeserver = hs }); + if (updateSw.ElapsedMilliseconds > 250) { + updateSw.Restart(); + StateHasChanged(); + } } catch (MatrixException e) { if (e is { ErrorCode: "M_UNKNOWN_TOKEN" }) _offlineSessions.Add(token); @@ -166,50 +177,9 @@ Small collection of tools to do not-so-everyday things. } sema.Release(); - - StateHasChanged(); }).ToList(); await Task.WhenAll(tasks); - - // var profileTasks = tokens.Select(async token => { - // UserInfo userInfo = new(); - // AuthenticatedHomeserverGeneric hs; - // Console.WriteLine($"Getting hs for {token.ToJson()}"); - // try { - // hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy); - // } - // catch (MatrixException e) { - // if (e.ErrorCode != "M_UNKNOWN_TOKEN") throw; - // _offlineSessions.Add(token); - // return; - // NavigationManager.NavigateTo("/InvalidSession?ctx=" + token.AccessToken); - // } - // catch (Exception e) { - // logger.LogError(e, $"Failed to instantiate AuthenticatedHomeserver for {token.ToJson()}, homeserver may be offline?", token.UserId); - // _offlineSessions.Add(token); - // return; - // } - // - // Console.WriteLine($"Got hs for {token.ToJson()}"); - // - // var roomCountTask = hs.GetJoinedRooms(); - // var profile = await hs.GetProfileAsync(hs.WhoAmI.UserId); - // userInfo.DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId; - // Console.WriteLine(profile.ToJson()); - // _sessions.Add(new() { - // UserInfo = new() { - // AvatarUrl = string.IsNullOrWhiteSpace(profile.AvatarUrl) ? "/blobfox_outage.gif" : hs.ResolveMediaUri(profile.AvatarUrl), - // RoomCount = (await roomCountTask).Count, - // DisplayName = profile.DisplayName ?? hs.WhoAmI.UserId - // }, - // UserAuth = token, - // ServerVersion = await (hs.FederationClient?.GetServerVersionAsync() ?? Task.FromResult(null)), - // Homeserver = hs - // }); - // }).ToList(); - // Console.WriteLine($"Waiting for {profileTasks.Count} profile tasks"); - // await Task.WhenAll(profileTasks); - // Console.WriteLine("Done waiting for profile tasks"); + await base.OnInitializedAsync(); } diff --git a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor index 3cb9e40..f9cbfa2 100644 --- a/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor +++ b/MatrixUtils.Web/Pages/Moderation/DraupnirProtectedRoomsEditor.razor @@ -59,7 +59,7 @@ if (hs is null) return; data = await hs.GetAccountDataAsync("org.matrix.mjolnir.protected_rooms"); StateHasChanged(); - foreach (var room in await hs.GetJoinedRooms()) { + var tasks = (await hs.GetJoinedRooms()).Select(async room => { var plTask = room.GetPowerLevelsAsync(); var roomNameTask = room.GetNameOrFallbackAsync(); var EditorRoomInfo = new EditorRoomInfo { @@ -71,7 +71,11 @@ Rooms.Add(EditorRoomInfo); StateHasChanged(); - } + return Task.CompletedTask; + }).ToList(); + await Task.WhenAll(tasks); + await Task.Delay(500); + StateHasChanged(); } private class DraupnirProtectedRoomsData { @@ -87,7 +91,7 @@ } private async Task Apply() { - Console.WriteLine(string.Join('\n', Rooms.Where(x=>x.IsProtected).Select(x=>x.Room.RoomId))); + Console.WriteLine(string.Join('\n', Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId))); data.Rooms = Rooms.Where(x => x.IsProtected).Select(x => x.Room.RoomId).ToList(); await hs.SetAccountDataAsync("org.matrix.mjolnir.protected_rooms", data); } diff --git a/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor index 775361a..9218c8c 100644 --- a/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor +++ b/MatrixUtils.Web/Pages/Moderation/UserRoomHistory.razor @@ -82,9 +82,7 @@ else { if (state is null) continue; if (!matchingStates.ContainsKey(state.Membership)) matchingStates.Add(state.Membership, new()); - var roomInfo = new RoomInfo() { - Room = room - }; + var roomInfo = new RoomInfo(room); matchingStates[state.Membership].Add(roomInfo); roomInfo.StateEvents.Add(new() { Type = RoomNameEventContent.EventId, diff --git a/MatrixUtils.Web/Pages/Rooms/Index.razor b/MatrixUtils.Web/Pages/Rooms/Index.razor index 1813908..d7a3569 100644 --- a/MatrixUtils.Web/Pages/Rooms/Index.razor +++ b/MatrixUtils.Web/Pages/Rooms/Index.razor @@ -69,14 +69,14 @@ protected override async Task OnInitializedAsync() { Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); if (Homeserver is null) return; - var rooms = await Homeserver.GetJoinedRooms(); + // var rooms = await Homeserver.GetJoinedRooms(); // SemaphoreSlim _semaphore = new(160, 160); GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId); var filter = await Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetBasicRoomInfo); var filterData = await Homeserver.GetFilterAsync(filter); - Rooms = new ObservableCollection(rooms.Select(x => new RoomInfo() { Room = x })); + // Rooms = new ObservableCollection(rooms.Select(room => new RoomInfo(room))); // foreach (var stateType in filterData.Room?.State?.Types ?? []) { // var tasks = Rooms.Select(async room => { // try { @@ -126,7 +126,7 @@ Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!"); - int maxUpdates = 50; + int maxUpdates = 50000; isInitialSync = false; while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { var (roomId, roomData) = queueEntry; @@ -139,9 +139,7 @@ } else { Console.WriteLine($"QueueWorker: encountered new room {roomId}!"); - room = new RoomInfo() { - Room = Homeserver.GetRoom(roomId) - }; + room = new RoomInfo(Homeserver.GetRoom(roomId), roomData.State?.Events); Rooms.Add(room); } @@ -156,14 +154,14 @@ Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!"); } - await Task.Delay(100); + // await Task.Delay(100); } Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}"); Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; - RenderContents |= queue.Count == 0; - await Task.Delay(Rooms.Count); + // RenderContents |= queue.Count == 0; + // await Task.Delay(Rooms.Count); } catch (Exception e) { Console.WriteLine("QueueWorker exception: " + e); diff --git a/MatrixUtils.Web/Pages/Rooms/Index2.razor b/MatrixUtils.Web/Pages/Rooms/Index2.razor new file mode 100644 index 0000000..ae31126 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Index2.razor @@ -0,0 +1,85 @@ +@page "/Rooms2" +@using LibMatrix.Responses +@using System.Collections.ObjectModel +@using System.ComponentModel +@using MatrixUtils.Abstractions +@using MatrixUtils.Web.Pages.Rooms.Index2Components +@inject ILogger logger +

Room list

+ + +@if (Data.Homeserver is null || Data.GlobalProfile is null) { +

Creating homeserver instance and fetching global profile...

+ return; +} + +
+ Main + DMs + By room type +
+
+ + @switch (SelectedTab) { + case Tab.Main: +

Main tab

+ + break; + case Tab.DMs: +

DMs tab

+ break; + case Tab.ByRoomType: +

By room type tab

+ break; + default: + throw new InvalidEnumArgumentException(); + } +
+
+ +@* Create new room *@ + + +@code { + + private Tab SelectedTab { + get => _selectedTab; + set { + _selectedTab = value; + StateHasChanged(); + } + } + + public RoomListViewData Data { get; set; } = new RoomListViewData(); + + protected override async Task OnInitializedAsync() { + Data.Homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); + if (Data.Homeserver is null) return; + var rooms = await Data.Homeserver.GetJoinedRooms(); + Data.GlobalProfile = await Data.Homeserver.GetProfileAsync(Data.Homeserver.WhoAmI.UserId); + + foreach (var room in rooms) { + Data.Rooms.Add(new RoomInfo(room)); + } + StateHasChanged(); + + await base.OnInitializedAsync(); + } + + private Tab _selectedTab = Tab.Main; + + private enum Tab { + Main, + DMs, + ByRoomType + } + + public class RoomListViewData { + public ObservableCollection Rooms { get; } = []; + + public UserProfileResponse? GlobalProfile { get; set; } + + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor new file mode 100644 index 0000000..4216824 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor @@ -0,0 +1,27 @@ +@using MatrixUtils.Abstractions +
+ + @Space.RoomName +
+ +@code { + + [Parameter] + public RoomInfo Space { get; set; } + + [Parameter] + public List OpenedSpaces { get; set; } + + protected override Task OnInitializedAsync() { + Space.PropertyChanged += (sender, args) => { StateHasChanged(); }; + return base.OnInitializedAsync(); + } + + public void ToggleSpace() { + if (OpenedSpaces.Contains(Space)) { + OpenedSpaces.Remove(Space); + } else { + OpenedSpaces.Add(Space); + } + } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css new file mode 100644 index 0000000..c174567 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/MainTabComponents/MainTabSpaceItem.razor.css @@ -0,0 +1,15 @@ +.spaceNameEllipsis { + padding-left: 8px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; + width: calc(100% - 38px); +} + +.spaceListItem { + display: block; + width: 100%; + height: 50px; +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor new file mode 100644 index 0000000..f4cf849 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2DMsTab.razor @@ -0,0 +1,53 @@ +@using MatrixUtils.Abstractions +@using System.Security.Cryptography +@using ArcaneLibs.Extensions +

RoomsIndex2MainTab

+ +
+
+
+ Uncategorised rooms + @foreach (var space in Data.Rooms.Where(x => x.RoomType == "m.space")) { +
+

@space.RoomName

+
+ } +
+
+

omae wa mou shindeiru

+
+
+
+ +@code { + + [CascadingParameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + Data.Rooms.CollectionChanged += (sender, args) => { + DebouncedStateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var newItem in args.NewItems) { + (newItem as RoomInfo).PropertyChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + }; + await base.OnInitializedAsync(); + } + + //debounce StateHasChanged, we dont want to reredner on every key stroke + + private CancellationTokenSource _debounceCts = new CancellationTokenSource(); + + private async Task DebouncedStateHasChanged() { + _debounceCts.Cancel(); + _debounceCts = new CancellationTokenSource(); + try { + await Task.Delay(100, _debounceCts.Token); + Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); + StateHasChanged(); + } + catch (TaskCanceledException) { } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor new file mode 100644 index 0000000..2b7c5ac --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor @@ -0,0 +1,198 @@ +@using MatrixUtils.Abstractions +@using System.Security.Cryptography +@using ArcaneLibs.Extensions +@using System.ComponentModel +@using System.Diagnostics +@using LibMatrix.EventTypes.Spec.State +@using MatrixUtils.Web.Pages.Rooms.Index2Components.MainTabComponents +@using Microsoft.AspNetCore.Components.Rendering +

RoomsIndex2MainTab

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

Placeholder for rooms list...

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

Placeholder for rooms list...

+
+
+
+ + +@code { + + [CascadingParameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + Data.Rooms.CollectionChanged += (sender, args) => { + DebouncedStateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var newItem in args.NewItems) { + (newItem as RoomInfo).PropertyChanged += OnRoomListChanged; + (newItem as RoomInfo).StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + }; + foreach (var newItem in Data.Rooms) { + newItem.PropertyChanged += OnRoomListChanged; + newItem.StateEvents.CollectionChanged += (sender, args) => { DebouncedStateHasChanged(); }; + } + + await base.OnInitializedAsync(); + StateHasChanged(); + } + + private void OnRoomListChanged(object? sender, PropertyChangedEventArgs e) { + if (e.PropertyName == "RoomName" || e.PropertyName == "RoomType") + DebouncedStateHasChanged(); + } + + private CancellationTokenSource _debounceCts = new CancellationTokenSource(); + + private async Task DebouncedStateHasChanged() { + _debounceCts.Cancel(); + _debounceCts = new CancellationTokenSource(); + try { + Console.WriteLine("DebouncedStateHasChanged - Waiting 50ms..."); + await Task.Delay(50, _debounceCts.Token); + Console.WriteLine("DebouncedStateHasChanged - Calling StateHasChanged!"); + StateHasChanged(); + } + catch (TaskCanceledException) { } + } + + private List GetTopLevelSpaces() { + var spaces = Data.Rooms.Where(x => x.RoomType == "m.space").OrderBy(x => x.RoomName).ToList(); + var allSpaceChildEvents = spaces.SelectMany(x => x.StateEvents.Where(y => + y.Type == SpaceChildEventContent.EventId && + y.RawContent!.Count > 0 + )).ToList(); + + Console.WriteLine($"Child count: {allSpaceChildEvents.Count}"); + + spaces.RemoveAll(x => allSpaceChildEvents.Any(y => y.StateKey == x.Room.RoomId)); + + if (allSpaceChildEvents.Count == 0) { + Console.WriteLine("No space children found, returning nothing..."); + return []; + } + + return spaces.ToList(); + } + + private List GetSpaceChildren(RoomInfo space) { + var childEvents = space.StateEvents.Where(x => + x.Type == SpaceChildEventContent.EventId && + x.RawContent!.Count > 0 + ).ToList(); + var children = childEvents.Select(x => Data.Rooms.FirstOrDefault(y => y.Room.RoomId == x.StateKey)).Where(x => x is not null).ToList(); + return children; + } + + private List GetSpaceChildSpaces(RoomInfo space) { + var children = GetSpaceChildren(space); + var childSpaces = children.Where(x => x.RoomType == "m.space").ToList(); + return childSpaces; + } + + private RoomInfo? SelectedSpace { get; set; } + private List OpenedSpaces { get; set; } = new List(); + + private RenderFragment RecursingSpaceChildren(RoomInfo space, List? parents = null, int depth = 0) { + parents ??= []; + var totalSw = Stopwatch.StartNew(); + var children = GetSpaceChildSpaces(space); + + var randomColor = RandomNumberGenerator.GetBytes(3).Append((byte)0x33).ToArray().AsHexString().Replace(" ", ""); + var isExpanded = OpenedSpaces.Contains(space); + + // Console.WriteLine($"RecursingSpaceChildren::FetchData - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); + + // var renderSw = Stopwatch.StartNew(); + var rf = new RenderFragment(builder => { + builder.OpenElement(0, "div"); + //space list entry render fragment + // builder.AddContent(1, SpaceListEntry(space)); + builder.OpenComponent(1); + builder.AddAttribute(2, "Space", space); + builder.AddAttribute(2, "OpenedSpaces", OpenedSpaces); + builder.CloseComponent(); + builder.CloseElement(); + //space children render fragment + if (isExpanded) { + builder.OpenElement(2, "div"); + builder.AddAttribute(3, "style", "padding-left: 10px;"); + foreach (var child in children) { + builder.AddContent(4, RecursingSpaceChildren(child, parents.Append(space).ToList(), depth + 1)); + } + + builder.CloseElement(); + } + }); + + // Console.WriteLine($"RecursingSpaceChildren::Render - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {renderSw.Elapsed}"); + if (totalSw.ElapsedMilliseconds > 20) + Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); + // Console.WriteLine($"RecursingSpaceChildren::Total - Depth: {depth}, Space: {space.RoomName}, Children: {children.Count} - {totalSw.Elapsed}"); + return rf; + } + + // private RenderFragment SpaceListEntry(RoomInfo space) { + // return builder => { + // { + // builder.OpenElement(0, "div"); + // builder.AddAttribute(1, "style", "display: block; width: 100%; height: 50px;"); + // builder.AddAttribute(2, "onclick", EventCallback.Factory.Create(this, () => { + // if (OpenedSpaces.Contains(space)) { + // OpenedSpaces.Remove(space); + // } + // else { + // OpenedSpaces.Add(space); + // } + // + // StateHasChanged(); + // })); + // { + // builder.OpenComponent(5); + // builder.AddAttribute(6, "Homeserver", Data.Homeserver); + // builder.AddAttribute(7, "MxcUri", space.RoomIcon); + // builder.AddAttribute(8, "Circular", true); + // builder.AddAttribute(9, "Width", 32); + // builder.AddAttribute(10, "Height", 32); + // builder.CloseComponent(); + // } + // { + // // room name, ellipsized + // builder.OpenElement(11, "span"); + // builder.AddAttribute(12, "class", "spaceNameEllipsis"); + // builder.AddContent(13, space.RoomName); + // builder.CloseElement(); + // } + // builder.CloseElement(); + // } + // }; + // } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2MainTab.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor new file mode 100644 index 0000000..bbc63eb --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Index2Components/RoomsIndex2SyncContainer.razor @@ -0,0 +1,202 @@ +@using LibMatrix.Helpers +@using LibMatrix.Responses +@using MatrixUtils.Abstractions +@using System.Diagnostics +@using System.Diagnostics.CodeAnalysis +@using LibMatrix.EventTypes.Spec.State +@using LibMatrix.Extensions +@using LibMatrix.Utilities +@using System.Collections.ObjectModel +@using ArcaneLibs +@inject ILogger logger +
RoomsIndex2SyncContainer
+@foreach (var (name, value) in _statusList) { +
[@name] @value.Status
+} + +@code { + + [Parameter] + public Index2.RoomListViewData Data { get; set; } = null!; + + private SyncHelper syncHelper; + + private Queue> queue = new(); + + private ObservableCollection<(string name, ObservableStatus value)> _statusList = new(); + + protected override async Task OnInitializedAsync() { + _statusList.CollectionChanged += (sender, args) => { + StateHasChanged(); + if (args.NewItems is { Count: > 0 }) + foreach (var item in args.NewItems) { + if (item is not (string name, ObservableStatus value)) continue; + value.PropertyChanged += (sender, args) => { + if(value.Show) StateHasChanged(); + }; + } + }; + + while (Data.Homeserver is null) { + await Task.Delay(100); + } + + await SetUpSync(); + } + + private async Task SetUpSync() { + var status = await GetOrAddStatus("Main"); + var syncHelpers = new Dictionary() { + ["Main"] = new SyncHelper(Data.Homeserver, logger) { + Timeout = 30000, + FilterId = await Data.Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetBasicRoomInfo), + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + } + }; + status.Status = "Initial sync... Checking server filter capability..."; + var syncRes = await syncHelpers["Main"].SyncAsync(); + if (!syncRes.Rooms?.Join?.Any(x => x.Value.State?.Events?.Any(y => y.Type == SpaceChildEventContent.EventId) ?? false) ?? true) { + status.Status = "Initial sync indicates that server supports filters, starting helpers!"; + syncHelpers.Add("SpaceRelations", new SyncHelper(Data.Homeserver, logger) { + Timeout = 30000, + FilterId = await Data.Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetSpaceRelations), + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + }); + + syncHelpers.Add("Profile", new SyncHelper(Data.Homeserver, logger) { + Timeout = 30000, + FilterId = await Data.Homeserver.GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetOwnMemberEvents), + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + }); + } + else status.Status = "Initial sync indicates that server does not support filters, continuing without extra filters!"; + + await HandleSyncResponse(syncRes); + + // profileSyncHelper = new SyncHelper(Homeserver, logger) { + // Timeout = 10000, + // Filter = profileUpdateFilter, + // MinimumDelay = TimeSpan.FromMilliseconds(5000) + // }; + // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); + RunQueueProcessor(); + foreach (var helper in syncHelpers) { + Console.WriteLine($"Starting sync loop for {helper.Key}"); + RunSyncLoop(helper.Value, helper.Key); + } + } + + private async Task RunQueueProcessor() { + var status = await GetOrAddStatus("QueueProcessor"); + var statusd = await GetOrAddStatus("QueueProcessor/D", show: false); + while (true) { + await Task.Delay(1000); + try { + var renderTimeSw = Stopwatch.StartNew(); + while (queue.Count == 0) { + var delay = 1000; + Console.WriteLine("Queue is empty, waiting..."); + // Status2 = $"Queue is empty, waiting for {delay}ms..."; + await Task.Delay(delay); + } + + status.Status = $"Queue no longer empty after {renderTimeSw.Elapsed}!"; + renderTimeSw.Restart(); + + int maxUpdates = 5000; + while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { + var (roomId, roomData) = queueEntry; + statusd.Status = $"Dequeued room {roomId}"; + RoomInfo room; + + if (Data.Rooms.Any(x => x.Room.RoomId == roomId)) { + room = Data.Rooms.First(x => x.Room.RoomId == roomId); + statusd.Status = $"{roomId} already known with {room.StateEvents?.Count ?? 0} state events"; + } + else { + statusd.Status = $"Eencountered new room {roomId}!"; + room = new RoomInfo(Data.Homeserver!.GetRoom(roomId), roomData.State?.Events); + Data.Rooms.Add(room); + } + + if (roomData.State?.Events is { Count: > 0 }) + room.StateEvents!.MergeStateEventLists(roomData.State.Events); + else { + statusd.Status = $"Could not merge state for {room.Room.RoomId} as new data contains no state events!"; + } + + // await Task.Delay(10); + } + + status.Status = $"Got {Data.Rooms.Count} rooms so far! {queue.Count} entries left in processing queue... Parsed last response in {renderTimeSw.Elapsed}"; + + // RenderContents |= queue.Count == 0; + // await Task.Delay(Data.Rooms.Count); + } + catch (Exception e) { + Console.WriteLine("QueueWorker exception: " + e); + } + } + } + + private async Task RunSyncLoop(SyncHelper syncHelper, string name = "Unknown") { + var status = await GetOrAddStatus($"SYNC/{name}"); + status.Status = $"Initial syncing..."; + + var syncs = syncHelper.EnumerateSyncAsync(); + await foreach (var sync in syncs) { + var sw = Stopwatch.StartNew(); + status.Status = $"[{DateTime.Now}] Got {Data.Rooms.Count} rooms so far! {sync.Rooms?.Join?.Count ?? 0} new updates!"; + + await HandleSyncResponse(sync); + status.Status += $"\nProcessed sync in {sw.ElapsedMilliseconds}ms, queue length: {queue.Count}"; + } + } + + private async Task HandleSyncResponse(SyncResponse? sync) { + if (sync?.Rooms?.Join is { Count: > 0 }) + foreach (var joinedRoom in sync.Rooms.Join) + queue.Enqueue(joinedRoom); + + if (sync.Rooms.Leave is { Count: > 0 }) + foreach (var leftRoom in sync.Rooms.Leave) + if (Data.Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) + Data.Rooms.Remove(Data.Rooms.First(x => x.Room.RoomId == leftRoom.Key)); + } + + private SemaphoreSlim _syncLock = new(1, 1); + + private async Task GetOrAddStatus(string name, bool show = true, bool log = true) { + await _syncLock.WaitAsync(); + try { + if (_statusList.Any(x => x.name == name)) + return _statusList.First(x => x.name == name).value; + var status = new ObservableStatus() { + Name = name, + Log = log, + Show = show + }; + _statusList.Add((name, status)); + return status; + } + finally { + _syncLock.Release(); + } + } + + private class ObservableStatus : NotifyPropertyChanged { + private string _status = "Initialising..."; + public string Name { get; set; } = "Unknown"; + public bool Show { get; set; } = true; + public bool Log { get; set; } = true; + + public string Status { + get => _status; + set { + if(SetField(ref _status, value) && Log) + Console.WriteLine($"[{Name}]: {value}"); + } + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor b/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor new file mode 100644 index 0000000..c94d0b0 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/PolicyListActivity.razor @@ -0,0 +1,158 @@ +@page "/Tools/PolicyListActivity" +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Diagnostics +@using LibMatrix.RoomTypes +@using LibMatrix.EventTypes.Common + + +@if (RoomData.Count == 0) +{ +

Loading...

+} +else + foreach (var room in RoomData) + { +

@room.Key

+ @foreach (var year in room.Value.OrderBy(x => x.Key)) + { +
@year.Key
+ + + } + } + + +@code { + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + public List FilteredRooms = new(); + + public Dictionary TestData { get; set; } = new(); + + public ActivityGraph.RGB MaxValue { get; set; } = new() + { + R = 255, G = 255, B = 255 + }; + + public Dictionary>> RoomData { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + var sw = Stopwatch.StartNew(); + await base.OnInitializedAsync(); + Homeserver = (await RMUStorage.GetCurrentSessionOrNavigate())!; + if (Homeserver is null) return; + + //random test data + for (DateOnly i = new DateOnly(2020, 1, 1); i < new DateOnly(2020, 12, 30); i = i.AddDays(Random.Shared.Next(5))) + { + TestData[i] = new() + { + R = (int)(Random.Shared.NextSingle() * 255), + G = (int)(Random.Shared.NextSingle() * 255), + B = (int)(Random.Shared.NextSingle() * 255) + }; + } + + StateHasChanged(); + // return; + + var rooms = await Homeserver.GetJoinedRooms(); + // foreach (var room in rooms) + // { + // var type = await room.GetRoomType(); + // if (type == "support.feline.policy.lists.msc.v1") + // { + // Console.WriteLine($"{room.RoomId} is policy list by type"); + // FilteredRooms.Add(room); + // } + // else if(await room.GetStateOrNullAsync(MjolnirShortcodeEventContent.EventId) is not null) + // { + // Console.WriteLine($"{room.RoomId} is policy list by shortcode"); + // FilteredRooms.Add(room); + // } + // } + var roomFilterTasks = rooms.Select(async room => + { + var type = await room.GetRoomType(); + if (type == "support.feline.policy.lists.msc.v1") + { + Console.WriteLine($"{room.RoomId} is policy list by type"); + return room; + } + else if (await room.GetStateOrNullAsync(MjolnirShortcodeEventContent.EventId) is not null) + { + Console.WriteLine($"{room.RoomId} is policy list by shortcode"); + return room; + } + + return null; + }).ToList(); + var filteredRooms = await Task.WhenAll(roomFilterTasks); + FilteredRooms.AddRange(filteredRooms.Where(x => x is not null).Cast()); + Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); + + var roomTasks = FilteredRooms.Select(FetchRoomHistory).ToList(); + await Task.WhenAll(roomTasks); + + Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}"); + Console.WriteLine($"Filtered {FilteredRooms.Count} rooms in {sw.ElapsedMilliseconds}ms"); + } + + public async Task FetchRoomHistory(GenericRoom room) + { + var roomName = await room.GetNameOrFallbackAsync(); + if (string.IsNullOrWhiteSpace(roomName)) roomName = room.RoomId; + if (!RoomData.ContainsKey(roomName)) + { + RoomData[roomName] = new(); + } + + //use timeline + var timeline = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000); + await foreach (var response in timeline) + { + Console.WriteLine($"Got {response.State.Count} state, {response.Chunk.Count} timeline"); + if (response.State.Count != 0) throw new Exception("Why the hell did we receive state events?"); + foreach (var message in response.Chunk) + { + if (!message.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + //OriginServerTs to datetime + var dt = DateTimeOffset.FromUnixTimeMilliseconds((long)message.OriginServerTs!.Value).DateTime; + var date = new DateOnly(dt.Year, dt.Month, dt.Day); + if (!RoomData[roomName].ContainsKey(date.Year)) + { + RoomData[roomName][date.Year] = new(); + } + + if (!RoomData[roomName][date.Year].ContainsKey(date)) + { + // Console.WriteLine($"Adding {date} to {roomName}"); + RoomData[roomName][date.Year][date] = new(); + } + + var rgb = RoomData[roomName][date.Year][date]; + if (message.RawContent?.Count == 0) rgb.R++; + else if (string.IsNullOrWhiteSpace(message.Unsigned?.ReplacesState)) rgb.G++; + else rgb.B++; + RoomData[roomName][date.Year][date] = rgb; + } + + var max = RoomData.SelectMany(x => x.Value.Values).Aggregate(new ActivityGraph.RGB(), (current, next) => new() + { + R = Math.Max(current.R, next.Average(x => x.Value.R)), + G = Math.Max(current.G, next.Average(x => x.Value.G)), + B = Math.Max(current.B, next.Average(x => x.Value.B)) + }); + MaxValue = new ActivityGraph.RGB( + r: Math.Max(max.R, Math.Max(max.G, max.B)), + g: Math.Max(max.R, Math.Max(max.G, max.B)), + b: Math.Max(max.R, Math.Max(max.G, max.B))); + Console.WriteLine($"Max value is {MaxValue.R} {MaxValue.G} {MaxValue.B}"); + StateHasChanged(); + await Task.Delay(100); + } + } + + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Tools/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/UserTrace.razor index b3a7487..d78c58a 100644 --- a/MatrixUtils.Web/Pages/Tools/UserTrace.razor +++ b/MatrixUtils.Web/Pages/Tools/UserTrace.razor @@ -80,18 +80,33 @@ Random.Shared.Shuffle(distinctRooms); rooms = new ObservableCollection(distinctRooms); rooms.CollectionChanged += (sender, args) => StateHasChanged(); + try { + var stateTasks = rooms.Select(async x => { + for (int i = 0; i < 10; i++) { + try { + return (x, await x.GetMembersListAsync(false)); + } + catch { + // + } + } - var stateTasks = rooms.Select(async x => (x, await x.GetMembersListAsync(false))).ToAsyncEnumerable(); + return (x, new List().ToFrozenSet()); + }).ToAsyncEnumerable(); - await foreach (var (room, state) in stateTasks) { - roomMembers.Add(room, state); - log.Add($"Got {state.Count} members for {room.RoomId}..."); + await foreach (var (room, state) in stateTasks) { + roomMembers.Add(room, state); + log.Add($"Got {state.Count} members for {room.RoomId}..."); + } + } + catch { + // } log.Add($"Done fetching members!"); - UserIDs.RemoveAll(x=>sessions.Any(y=>y.UserId == x)); - + UserIDs.RemoveAll(x => sessions.Any(y => y.UserId == x)); + StateHasChanged(); Console.WriteLine("Rerendered!"); await base.OnInitializedAsync(); @@ -105,7 +120,6 @@ matches[userId].Add(new() { Event = events.First(x => x.StateKey == userId && x.Type == RoomMemberEventContent.EventId), Room = room, - }); } } @@ -132,6 +146,7 @@ private class Matches { public GenericRoom Room; + public StateEventResponse Event; // public } diff --git a/MatrixUtils.Web/Pages/User/DMManager.razor b/MatrixUtils.Web/Pages/User/DMManager.razor index df5cd6b..80bf3b2 100644 --- a/MatrixUtils.Web/Pages/User/DMManager.razor +++ b/MatrixUtils.Web/Pages/User/DMManager.razor @@ -2,6 +2,7 @@ @using LibMatrix.EventTypes.Spec.State @using LibMatrix.Responses @using MatrixUtils.Abstractions +@using LibMatrix

Direct Messages


@@ -36,11 +37,19 @@ Status = "Loading DM list from account data..."; var dms = await Homeserver.GetAccountDataAsync>>("m.direct"); DMRooms.Clear(); - foreach (var (userId, rooms) in dms) { + var userTasks = dms.Select(async kv => { + var (userId, rooms) = kv; var roomList = new List(); - DMRooms.Add(await Homeserver.GetProfileAsync(userId), roomList); + UserProfileResponse? profile = null; + try { + profile = await Homeserver.GetProfileAsync(userId); + } + catch (MatrixException e) { + if (e is { ErrorCode: "M_UNKNOWN" }) profile = new UserProfileResponse() { DisplayName = $"{userId}: {e.Error}" }; + } + foreach (var room in rooms) { - var roomInfo = new RoomInfo() { Room = Homeserver.GetRoom(room) }; + var roomInfo = new RoomInfo(Homeserver.GetRoom(room)); roomList.Add(roomInfo); roomInfo.StateEvents.Add(new() { Type = RoomNameEventContent.EventId, @@ -50,8 +59,13 @@ RoomId = room, Sender = null, EventId = null }); } + + DMRooms.Add(profile ?? new() { DisplayName = userId }, roomList); StateHasChanged(); - } + }).ToList(); + + await Task.WhenAll(userTasks); + await Task.Delay(500); StateHasChanged(); Status = null; diff --git a/MatrixUtils.Web/Pages/User/DMSpace.razor b/MatrixUtils.Web/Pages/User/DMSpace.razor index 519cfff..e3dba30 100644 --- a/MatrixUtils.Web/Pages/User/DMSpace.razor +++ b/MatrixUtils.Web/Pages/User/DMSpace.razor @@ -1,11 +1,14 @@ @page "/User/DMSpace/Setup" @using LibMatrix +@using LibMatrix.Responses +@using MatrixUtils.Abstractions @using MatrixUtils.LibDMSpace @using MatrixUtils.LibDMSpace.StateEvents @using MatrixUtils.Web.Pages.User.DMSpaceStages +@using System.Text.Json.Serialization

DM Space Management


- + @switch (Stage) { case -1:

Initialising...

@@ -41,36 +44,29 @@ } } - public AuthenticatedHomeserverGeneric? Homeserver { get; set; } - public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } - - [Parameter] - public DMSpace? DmSpace { get; set; } + public DMSpace? DMSpaceRootPage { get; set; } protected override async Task OnInitializedAsync() { if (NavigationManager.Uri.Contains("?stage=")) { - NavigationManager.NavigateTo("/User/DMSpace", true); + NavigationManager.NavigateTo("/User/DMSpace/Setup", true); } - DmSpace = this; - Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; + DMSpaceRootPage = this; + SetupData.Homeserver ??= await RMUStorage.GetCurrentSessionOrNavigate(); + if (SetupData.Homeserver is null) return; try { - DmSpaceConfiguration = await Homeserver.GetAccountDataAsync("gay.rory.dm_space"); - var room = Homeserver.GetRoom(DmSpaceConfiguration.DMSpaceId); - await room.GetStateAsync(DMSpaceInfo.EventId); + SetupData.DmSpaceConfiguration = await SetupData.Homeserver.GetAccountDataAsync("gay.rory.dm_space"); + var room = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); + await room.GetStateAsync(DMSpaceInfo.EventId); Stage = 1; } catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") { + if (e.ErrorCode is "M_NOT_FOUND" or "M_FORBIDDEN") { Stage = 0; - DmSpaceConfiguration = new(); + SetupData.DmSpaceConfiguration = new(); } else throw; } - catch (Exception e) { - throw; - } finally { StateHasChanged(); } @@ -82,4 +78,27 @@ await base.OnParametersSetAsync(); } + public DMSpaceSetupData SetupData { get; set; } = new(); + + public class DMSpaceSetupData { + + public AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + public DMSpaceConfiguration? DmSpaceConfiguration { get; set; } + + public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); + + public Dictionary? Spaces; + + public Dictionary>? DMRooms; + + public RoomInfo? DMSpaceRoomInfo { get; set; } + + + public class UserProfileWithId : UserProfileResponse { + [JsonIgnore] + public string Id { get; set; } + } + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor index 49fd5b4..5f6508c 100644 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage0.razor @@ -4,7 +4,7 @@

This wizard will help you set up a DM space.

This is useful for eg. sharing DM rooms across multiple accounts.


-Get started +Get started @code { diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor index 6131617..2176467 100644 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage1.razor @@ -6,30 +6,40 @@ @using MatrixUtils.LibDMSpace.StateEvents @using Microsoft.Extensions.Primitives @using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State +@using MatrixUtils.Abstractions DM Space setup tool - stage 1: Configure space

You will need a space to use for DM rooms.

-@if (DmSpace is not null) { -

- Selected space: - - @foreach (var (id, name) in spaces) { - - } - -

-

- - Create sub-spaces per user -

+@if (SetupData is not null) { + if (SetupData.Spaces is not null) { +

+ Selected space: + + + @foreach (var (id, roomInfo) in SetupData.Spaces) { + + } + +

+

+ + Create sub-spaces per user +

+ +
+ Disband + Next + } + else { +

Discovering spaces, please wait...

+ } } else { - Error: DmSpaceConfiguration is null! + Error: Setup data is null! } -
-Next @if (!string.IsNullOrWhiteSpace(Status)) {

@Status

@@ -45,84 +55,97 @@ else { } } - private Dictionary spaces = new() { { "", "New space" } }; private string? _status; [CascadingParameter] - public DMSpace? DmSpace { get; set; } + public DMSpace.DMSpaceSetupData SetupData { get; set; } - public DMSpaceInfo? DmSpaceInfo { get; set; } = new(); + SemaphoreSlim _semaphoreSlim = new(1, 1); protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - } - - SemaphoreSlim _semaphoreSlim = new(1, 1); - protected override async Task OnParametersSetAsync() { - if (DmSpace is null) + if (SetupData is null) return; + await _semaphoreSlim.WaitAsync(); - DmSpace.DmSpaceConfiguration ??= new(); - if (spaces.Count == 1) { - Status = "Looking for spaces..."; - var userRoomsEnum = DmSpace.Homeserver.GetJoinedRoomsByType("m.space"); - List userRooms = new(); - await foreach (var room in userRoomsEnum) { - userRooms.Add(room); - } - var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); - await foreach(var room in roomChecks) - if(room.HasValue) - spaces.TryAdd(room.Value.id, room.Value.name); - - Status = "Done!"; + + Dictionary spaces = []; + SetupData.DmSpaceConfiguration ??= new(); + + Status = "Looking for spaces..."; + var userRoomsEnum = SetupData.Homeserver!.GetJoinedRoomsByType("m.space"); + + List userRooms = new(); + await foreach (var room in userRoomsEnum) { + userRooms.Add(room); } + + var roomChecks = userRooms.Select(GetFeasibleSpaces).ToAsyncEnumerable(); + await foreach (var room in roomChecks) + if (room.HasValue) + spaces.TryAdd(room.Value.id, room.Value.roomInfo); + + SetupData.Spaces = spaces; + + Status = "Done!"; _semaphoreSlim.Release(); await base.OnParametersSetAsync(); } private async Task Execute() { - if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { - var crr = CreateRoomRequest.CreatePrivate(DmSpace.Homeserver, "Direct Messages"); - crr.CreationContentBaseType.Type = "m.space"; - DmSpace.DmSpaceConfiguration.DMSpaceId = (await DmSpace.Homeserver.CreateRoom(crr)).RoomId; + if (string.IsNullOrWhiteSpace(SetupData!.DmSpaceConfiguration!.DMSpaceId)) { + var createRoomRequest = CreateRoomRequest.CreatePrivate(SetupData.Homeserver!, "Direct Messages"); + createRoomRequest.CreationContentBaseType.Type = "m.space"; + SetupData.DmSpaceConfiguration.DMSpaceId = (await SetupData.Homeserver!.CreateRoom(createRoomRequest)).RoomId; } - await DmSpace.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, DmSpace.DmSpaceConfiguration); - var space = DmSpace.Homeserver.GetRoom(DmSpace.DmSpaceConfiguration.DMSpaceId); - await space.SendStateEventAsync(DMSpaceInfo.EventId, DmSpaceInfo); + + await SetupData.Homeserver!.SetAccountDataAsync(DMSpaceConfiguration.EventId, SetupData.DmSpaceConfiguration); + var space = SetupData.Homeserver.GetRoom(SetupData.DmSpaceConfiguration.DMSpaceId); + await space.SendStateEventAsync(DMSpaceInfo.EventId, SetupData.DmSpaceInfo); + SetupData.DMSpaceRoomInfo = new RoomInfo(space); + await SetupData.DMSpaceRoomInfo.FetchAllStateAsync(); NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=2"); } - public async Task<(string id, string name)?> GetFeasibleSpaces(GenericRoom room) { + public async Task<(string id, RoomInfo roomInfo)?> GetFeasibleSpaces(GenericRoom room) { try { - var pls = await room.GetPowerLevelsAsync(); - if (!pls.UserHasStatePermission(DmSpace.Homeserver.WhoAmI.UserId, "m.space.child")) { + var ri = new RoomInfo(room); + + await foreach(var evt in room.GetFullStateAsync()) + ri.StateEvents.Add(evt); + + var powerLevels = (await ri.GetStateEvent(RoomPowerLevelEventContent.EventId)).TypedContent as RoomPowerLevelEventContent; + if (!powerLevels.UserHasStatePermission(SetupData.Homeserver.WhoAmI.UserId, SpaceChildEventContent.EventId)) { Console.WriteLine($"No permission to send m.space.child in {room.RoomId}..."); return null; } - var roomName = await room.GetNameAsync(); - Status = $"Found viable space: {roomName}"; - if (string.IsNullOrWhiteSpace(DmSpace.DmSpaceConfiguration.DMSpaceId)) { - try { - var dsi = await DmSpace.Homeserver.GetRoom(room.RoomId).GetStateOrNullAsync(DMSpaceInfo.EventId) ?? new DMSpaceInfo(); - if (await room.GetStateOrNullAsync(DMSpaceInfo.EventId) is not null && dsi is not null) { - DmSpace.DmSpaceConfiguration.DMSpaceId = room.RoomId; - DmSpaceInfo = dsi; - } - } - catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"{room.RoomId} is not a DM space."); - else throw; + + Status = $"Found viable space: {ri.RoomName}"; + if (!string.IsNullOrWhiteSpace(SetupData.DmSpaceConfiguration!.DMSpaceId)) { + if (await room.GetStateOrNullAsync(DMSpaceInfo.EventId) is { } dsi) { + SetupData.DmSpaceConfiguration.DMSpaceId = room.RoomId; + SetupData.DmSpaceInfo = dsi; + Console.WriteLine(dsi.ToJson(ignoreNull: true)); } } - return (room.RoomId, roomName); + + if (ri.RoomName == room.RoomId) + ri.RoomName = await room.GetNameOrFallbackAsync(); + + return (room.RoomId, ri); } catch (MatrixException e) { if (e.ErrorCode == "M_NOT_FOUND") Console.WriteLine($"m.room.power_levels does not exist in {room.RoomId}!!!"); else throw; } + return null; } + private async Task Disband() { + var space = new DMSpaceRoom(SetupData.Homeserver, SetupData.DmSpaceConfiguration.DMSpaceId); + await space.DisbandDMSpace(); + NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true); + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor index 5a53347..a70e9c5 100644 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage2.razor @@ -17,18 +17,23 @@

@Status

} -@if (DmSpace is not null) { - @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.Id)) { - - @foreach (var roomInfo in room) { - - Reassign - +@if (SetupData is not null) { + if (SetupData.DMRooms is { Count: > 0 }) { + @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.Id)) { + + @foreach (var roomInfo in room) { + + Reassign + + } } } + else { +

DM room list is loading, please wait...

+ } } else { - Error: DmSpaceConfiguration is null! + Error: DMSpaceRootPage is null! }
@@ -88,26 +93,21 @@ else { private RoomInfo? _dmToReassign; [CascadingParameter] - public DMSpace? DmSpace { get; set; } + public DMSpace.DMSpaceSetupData SetupData { get; set; } - private Dictionary> dmRooms { get; set; } = new(); - private Dictionary> duplicateDmRooms { get; set; } = new(); - private Dictionary> roomMembers { get; set; } = new(); - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - } + private Dictionary> duplicateDmRooms { get; set; } = new(); + private Dictionary> roomMembers { get; set; } = new(); SemaphoreSlim _semaphore = new(1, 1); - protected override async Task OnParametersSetAsync() { - if (DmSpace is null) + protected override async Task OnInitializedAsync() { + if (SetupData is null) return; await _semaphore.WaitAsync(); DmToReassign = null; - var hs = DmSpace.Homeserver; + var hs = SetupData.Homeserver; Status = "Loading DM list from account data..."; - var dms = await DmSpace.Homeserver.GetAccountDataAsync>>("m.direct"); + var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); Status = "Optimising DM list from account data..."; var joinedRooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList(); foreach (var (user, rooms) in dms) { @@ -116,18 +116,22 @@ else { if (!joinedRooms.Contains(roomId)) rooms.RemoveAt(i); } + dms[user] = rooms.Distinct().ToList(); } - dms.RemoveAll((x, y) => y is {Count: 0}); - await DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); - dmRooms.Clear(); + + dms.RemoveAll((x, y) => y is { Count: 0 }); + await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); Status = "DM list optimised, fetching info..."; + + SetupData.DMRooms = new Dictionary>(); + var results = dms.Select(async x => { var (userId, rooms) = x; - UserProfileWithId userProfile; + DMSpace.DMSpaceSetupData.UserProfileWithId userProfile; try { - var profile = await DmSpace.Homeserver.GetProfileAsync(userId); + var profile = await SetupData.Homeserver.GetProfileAsync(userId); userProfile = new() { AvatarUrl = profile.AvatarUrl, Id = userId, @@ -141,32 +145,35 @@ else { Id = userId }; } + var roomList = new List(); var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); await foreach (var result in tasks) roomList.Add(result); return (userProfile, roomList); - // StateHasChanged(); + // StateHasChanged(); }).ToAsyncEnumerable(); await foreach (var res in results) { - dmRooms.Add(res.userProfile, res.roomList); - // Status = $"Listed {dmRooms.Count} users"; + SetupData.DMRooms.Add(res.userProfile, res.roomList); + // Status = $"Listed {dmRooms.Count} users"; } + _semaphore.Release(); - var duplicateDmRoomIds = new Dictionary>(); - foreach (var (user, rooms) in dmRooms) { + var duplicateDmRoomIds = new Dictionary>(); + foreach (var (user, rooms) in SetupData.DMRooms) { foreach (var roomInfo in rooms) { if (!duplicateDmRoomIds.ContainsKey(roomInfo.Room.RoomId)) duplicateDmRoomIds.Add(roomInfo.Room.RoomId, new()); duplicateDmRoomIds[roomInfo.Room.RoomId].Add(user); } } + duplicateDmRoomIds.RemoveAll((x, y) => y.Count == 1); foreach (var (roomId, users) in duplicateDmRoomIds) { - duplicateDmRooms.Add(dmRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); + duplicateDmRooms.Add(SetupData.DMRooms.First(x => x.Value.Any(x => x.Room.RoomId == roomId)).Value.First(x => x.Room.RoomId == roomId), users); } - // StateHasChanged(); + // StateHasChanged(); Status = null; await base.OnParametersSetAsync(); } @@ -176,34 +183,29 @@ else { } private async Task GetRoomInfo(GenericRoom room) { - var roomInfo = new RoomInfo() { - Room = room - }; + var roomInfo = new RoomInfo(room); + await roomInfo.FetchAllStateAsync(); roomMembers[roomInfo] = new(); - roomInfo.CreationEventContent = await room.GetCreateEventAsync(); - try { - roomInfo.RoomName = await room.GetNameAsync(); - } - catch { } + // roomInfo.CreationEventContent = await room.GetCreateEventAsync(); + + if(roomInfo.RoomName == room.RoomId) + try { + roomInfo.RoomName = await room.GetNameOrFallbackAsync(); + } + catch { } var membersEnum = room.GetMembersEnumerableAsync(true); await foreach (var member in membersEnum) if (member.TypedContent is RoomMemberEventContent memberEvent) roomMembers[roomInfo].Add(new() { DisplayName = memberEvent.DisplayName, AvatarUrl = memberEvent.AvatarUrl, Id = member.StateKey }); - - if (string.IsNullOrWhiteSpace(roomInfo.RoomName) || roomInfo.RoomName == room.RoomId) { - List displayNames = new List(); - foreach (var member in roomMembers[roomInfo]) - if (!string.IsNullOrWhiteSpace(member.DisplayName)) - displayNames.Add(member.DisplayName); - roomInfo.RoomName = string.Join(", ", displayNames); - } + try { string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; if (room is not null) roomInfo.RoomIcon = roomIcon; } catch { } + return roomInfo; } @@ -214,29 +216,25 @@ else { } private async Task SetRoomAssignment(string roomId, string userId) { - var hs = DmSpace.Homeserver; + var hs = SetupData.Homeserver; Status = "Loading DM list from account data..."; - var dms = await DmSpace.Homeserver.GetAccountDataAsync>>("m.direct"); + var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); Status = "Updating DM list from account data..."; foreach (var (user, rooms) in dms) { rooms.RemoveAll(x => x == roomId); dms[user] = rooms.Distinct().ToList(); } - if(!dms.ContainsKey(userId)) + + if (!dms.ContainsKey(userId)) dms.Add(userId, new()); dms[userId].Add(roomId); - dms.RemoveAll((x, y) => y is {Count: 0}); - await DmSpace.Homeserver.SetAccountDataAsync("m.direct", dms); + dms.RemoveAll((x, y) => y is { Count: 0 }); + await SetupData.Homeserver.SetAccountDataAsync("m.direct", dms); duplicateDmRooms.RemoveAll((x, y) => x.Room.RoomId == roomId); StateHasChanged(); if (duplicateDmRooms.Count == 0) await OnParametersSetAsync(); } - private class UserProfileWithId : UserProfileResponse { - [JsonIgnore] - public string Id { get; set; } - } - -} \ No newline at end of file +} diff --git a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor index 9307f6a..865e956 100644 --- a/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor +++ b/MatrixUtils.Web/Pages/User/DMSpaceStages/DMSpaceStage3.razor @@ -18,15 +18,15 @@

@Status

} -@if (DmSpace is not null) { - @if (dmSpaceInfo is not null && dmSpaceRoomInfo is not null) { +@if (SetupData is not null) { + @if (SetupData.DMSpaceRoomInfo is not null) {

- + Create sub-spaces per user

- @if (!dmSpaceInfo.LayerByUser) { - - @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { + @if (!SetupData.DmSpaceInfo.LayerByUser) { + + @foreach (var (userId, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) { @foreach (var roomInfo in room) {
@@ -35,10 +35,16 @@ } } else { - - @foreach (var (userId, room) in dmRooms.OrderBy(x => x.Key.RoomName)) { + + @foreach (var (user, room) in SetupData.DMRooms.OrderBy(x => x.Key.DisplayName)) {
- + @{ + RoomInfo fakeRoom = new(SetupData.DMSpaceRoomInfo.Room) { + RoomName = user.DisplayName ?? user.Id, + RoomIcon = user.AvatarUrl + }; + } +
@foreach (var roomInfo in room) {
@@ -49,11 +55,11 @@ } } else { - Error: dmSpaceInfo is null! + Error: SetupData.DMSpaceRoomInfo is null! } } else { - Error: DmSpaceConfiguration is null! + Error: DMSpaceRootPageConfiguration is null! }
@@ -72,83 +78,75 @@ else { private string? _status; [CascadingParameter] - public DMSpace? DmSpace { get; set; } - - private Dictionary> dmRooms { get; set; } = new(); - private DMSpaceInfo? dmSpaceInfo { get; set; } - private RoomInfo? dmSpaceRoomInfo { get; set; } - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - } + public DMSpace.DMSpaceSetupData SetupData { get; set; } SemaphoreSlim _semaphore = new(1, 1); - protected override async Task OnParametersSetAsync() { - if (DmSpace is null) + protected override async Task OnInitializedAsync() { + if (SetupData is null) return; await _semaphore.WaitAsync(); - var hs = DmSpace.Homeserver; - var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); - dmSpaceRoomInfo = new() { - RoomName = await dmSpaceRoom.GetNameAsync(), - CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), - RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - Room = dmSpaceRoom - }; - dmSpaceInfo = await dmSpaceRoom.GetDmSpaceInfo(); - Status = "Loading DM list from account data..."; - var dms = await DmSpace.Homeserver.GetAccountDataAsync>>("m.direct"); - dmRooms.Clear(); + var hs = SetupData.Homeserver; + // var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration.DMSpaceId); + // SetupData. + // dmSpaceRoomInfo = new() { + // RoomName = await dmSpaceRoom.GetNameAsync(), + // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync(), + // RoomIcon = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + // Room = dmSpaceRoom + // }; + // dmSpaceInfo = await dmSpaceRoom.GetDMSpaceInfo(); + // Status = "Loading DM list from account data..."; + // var dms = await SetupData.Homeserver.GetAccountDataAsync>>("m.direct"); Status = "DM list optimised, fetching info..."; - var results = dms.Select(async x => { - var (userId, rooms) = x; - UserProfileWithId userProfile; - try { - var profile = await DmSpace.Homeserver.GetProfileAsync(userId); - userProfile = new() { - AvatarUrl = profile.AvatarUrl, - Id = userId, - DisplayName = profile.DisplayName - }; - } - catch { - userProfile = new() { - AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", - DisplayName = userId, - Id = userId - }; - } - var roomList = new List(); - var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); - await foreach (var result in tasks) - roomList.Add(result); - return (userProfile, roomList); - }).ToAsyncEnumerable(); - await foreach (var res in results) { - dmRooms.Add(new RoomInfo() { - Room = dmSpaceRoom, - RoomIcon = res.userProfile.AvatarUrl, - RoomName = res.userProfile.DisplayName, - CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() - }, res.roomList); - } + // var results = dms.Select(async x => { + // var (userId, rooms) = x; + // UserProfileWithId userProfile; + // try { + // var profile = await SetupData.Homeserver.GetProfileAsync(userId); + // userProfile = new() { + // AvatarUrl = profile.AvatarUrl, + // Id = userId, + // DisplayName = profile.DisplayName + // }; + // } + // catch { + // userProfile = new() { + // AvatarUrl = "mxc://feline.support/uUxBwaboPkMGtbZcAGZaIzpK", + // DisplayName = userId, + // Id = userId + // }; + // } + // var roomList = new List(); + // var tasks = rooms.Select(x => GetRoomInfo(hs.GetRoom(x))).ToAsyncEnumerable(); + // await foreach (var result in tasks) + // roomList.Add(result); + // return (userProfile, roomList); + // }).ToAsyncEnumerable(); + // await foreach (var res in results) { + // dmRooms.Add(new RoomInfo() { + // Room = dmSpaceRoom, + // RoomIcon = res.userProfile.AvatarUrl, + // RoomName = res.userProfile.DisplayName, + // CreationEventContent = await dmSpaceRoom.GetCreateEventAsync() + // }, res.roomList); + // } + await SetupData.DMSpaceRoomInfo!.FetchAllStateAsync(); _semaphore.Release(); Status = null; await base.OnParametersSetAsync(); } private async Task Execute() { - var hs = DmSpace.Homeserver; - var dmSpaceRoom = new DMSpaceRoom(hs, DmSpace.DmSpaceConfiguration.DMSpaceId); + var hs = SetupData.Homeserver; + var dmSpaceRoom = new DMSpaceRoom(hs, SetupData.DmSpaceConfiguration!.DMSpaceId!); + await dmSpaceRoom.ImportNativeDMs(); NavigationManager.NavigateTo("/User/DMSpace/Setup?stage=3"); } private async Task GetRoomInfo(GenericRoom room) { - var roomInfo = new RoomInfo() { - Room = room - }; + var roomInfo = new RoomInfo(room); var roomMembers = new List(); roomInfo.CreationEventContent = await room.GetCreateEventAsync(); try { @@ -168,12 +166,14 @@ else { displayNames.Add(member.DisplayName); roomInfo.RoomName = string.Join(", ", displayNames); } + try { string? roomIcon = (await room.GetAvatarUrlAsync())?.Url; if (room is not null) roomInfo.RoomIcon = roomIcon; } catch { } + return roomInfo; } diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor index 79b83ae..129f706 100644 --- a/MatrixUtils.Web/Pages/User/Profile.razor +++ b/MatrixUtils.Web/Pages/User/Profile.razor @@ -110,8 +110,7 @@ var room = Homeserver.GetRoom(roomId); var roomNameTask = room.GetNameOrFallbackAsync(); var roomIconTask = room.GetAvatarUrlAsync(); - var roomInfo = new RoomInfo() { - Room = room, + var roomInfo = new RoomInfo(room) { OwnMembership = roomProfile }; try { diff --git a/MatrixUtils.Web/Shared/ActivityGraph.razor b/MatrixUtils.Web/Shared/ActivityGraph.razor new file mode 100644 index 0000000..51fb539 --- /dev/null +++ b/MatrixUtils.Web/Shared/ActivityGraph.razor @@ -0,0 +1,148 @@ +@using System.Drawing +@using System.Runtime.InteropServices +@using System.Diagnostics + +@if (Data is { Count: > 0 }) +{ + @* 12*5=60 *@ +
+ @* row 0: month labels with colspan *@ + @* @foreach (var month in Enumerable.Range(1, 12)) *@ + @* { *@ + @*
*@ + @* *@ + @*
*@ + @* } *@ + + @* column 0: day labels *@ + @* @for (var i = 0; i < 7; i++) *@ + @* { *@ + @*
*@ + @* @(((DayOfWeek)i).ToString()[..3]) *@ + @*
*@ + @* } *@ + + +
Jan
+
Feb
+
Mar
+
Apr
+
May
+
Jun
+
Jul
+
Aug
+
Sep
+
Oct
+
Nov
+
Dec
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+ + + @* pad activity cell dates... *@ +
+ + @* the actual activity cells *@ + + @code{ + bool needsBorder = false; + } + + @for (DateOnly date = new DateOnly(Data.Keys.First().Year, 1, 1); date <= new DateOnly(Data.Keys.First().Year, 1, 1).AddYears(1).AddDays(-1); date = date.AddDays(1)) + { + var hasData = Data.TryGetValue(date, out var color); + var needsTopBorder = date.Day == 1 && date.Month != 1 && date.DayOfWeek != DayOfWeek.Sunday; + if (date.DayOfWeek == DayOfWeek.Sunday) + needsBorder = date.AddDays(7).Day <= 7 && date.Month != 12; + var needsLeftBorder = date.Day <= 7; + +
+ @if (hasData) + { +
+
+ } + else + { +
+
+ } +
+ } +
+} + + +@code { + private Dictionary _data = new(); + private RGB? _globalMax = null; + + [Parameter] + public Dictionary Data + { + get => _data; + set + { + // var sw = Stopwatch.StartNew(); + if (value is not { Count: > 0 }) return; + // Console.WriteLine($"Recalculating activity graph ({value.Count} datapoints)..."); + + + // var year = (int)value.Keys.Average(x => x.Year); + // value = value + // .Where(x => x.Key.Year == year) + // .OrderBy(x => x.Key) + // .ToDictionary(x => x.Key, x => x.Value); + + _data = value; + // Console.WriteLine($"Recalculated activity graph in {sw.Elapsed}"); + // StateHasChanged(); + } + } + + [Parameter] + public RGB GlobalMax + { + get + { + if (_globalMax is not null) return _globalMax.Value; + if (Data is not { Count: > 0 }) return new RGB() { R = 255, G = 255, B = 255 }; + return new RGB() + { + R = Data.Values.Max(x => x.R), + G = Data.Values.Max(x => x.G), + B = Data.Values.Max(x => x.B) + }; + } + set => _globalMax = value; + } + + [Parameter] public string RLabel { get; set; } = "R"; + [Parameter] public string GLabel { get; set; } = "G"; + [Parameter] public string BLabel { get; set; } = "B"; + + [StructLayout(LayoutKind.Sequential, Size = sizeof(float) * 3, Pack = 1)] + public struct RGB() + { + public float R = 0; + public float G = 0; + public float B = 0; + + public RGB(float r, float g, float b) : this() + { + R = r; + G = g; + B = b; + } + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/ActivityGraph.razor.css b/MatrixUtils.Web/Shared/ActivityGraph.razor.css new file mode 100644 index 0000000..d8e543c --- /dev/null +++ b/MatrixUtils.Web/Shared/ActivityGraph.razor.css @@ -0,0 +1,16 @@ +.activity-cell-container { + width: 100%; + height: 100%; + align-content: center; + justify-content: center; +} + +.activity-cell { + width: 85%; + height: 85%; + border-radius: 5px; +} + +.day-label { + grid-column: 1; +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/MainLayout.razor b/MatrixUtils.Web/Shared/MainLayout.razor index d8bf411..41c3d69 100644 --- a/MatrixUtils.Web/Shared/MainLayout.razor +++ b/MatrixUtils.Web/Shared/MainLayout.razor @@ -8,8 +8,8 @@
- Git - Matrix + Git + Matrix
diff --git a/MatrixUtils.Web/Shared/MxcImage.razor b/MatrixUtils.Web/Shared/MxcImage.razor index f31c19f..e651c3f 100644 --- a/MatrixUtils.Web/Shared/MxcImage.razor +++ b/MatrixUtils.Web/Shared/MxcImage.razor @@ -30,6 +30,7 @@ StateHasChanged(); } } + [Parameter] public RemoteHomeserver? Homeserver { get; set; } @@ -41,7 +42,7 @@ } } - private string StyleString => $"{Style} {(Circular ? "border-radius: 50%;" : "")} {(Width.HasValue ? $"width: {Width}px;" : "")} {(Height.HasValue ? $"height: {Height}px;" : "")}"; + private string StyleString => $"{Style} {(Circular ? "border-radius: 50%;" : "")} {(Width.HasValue ? $"width: {Width}px;" : "")} {(Height.HasValue ? $"height: {Height}px;" : "")} object-fit: cover;"; private static readonly string Prefix = "mxc://"; private static readonly int PrefixLength = Prefix.Length; diff --git a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor index 9c481e3..6954990 100644 --- a/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor +++ b/MatrixUtils.Web/Shared/RoomListComponents/RoomListSpace.razor @@ -48,9 +48,7 @@ if (Breadcrumbs.Contains(room.RoomId)) continue; var roomInfo = KnownRooms.FirstOrDefault(x => x.Room.RoomId == room.RoomId); if (roomInfo is null) { - roomInfo = new RoomInfo() { - Room = room - }; + roomInfo = new RoomInfo(room); KnownRooms.Add(roomInfo); } if(joinedRooms.Any(x=>x.RoomId == room.RoomId)) diff --git a/MatrixUtils.Web/Shared/UserListItem.razor b/MatrixUtils.Web/Shared/UserListItem.razor index 525296e..daa9c9e 100644 --- a/MatrixUtils.Web/Shared/UserListItem.razor +++ b/MatrixUtils.Web/Shared/UserListItem.razor @@ -2,8 +2,9 @@ @using LibMatrix.EventTypes.Spec.State @using LibMatrix.Homeservers @using LibMatrix.Responses +@using ArcaneLibs
- + @User?.DisplayName
@@ -27,6 +28,8 @@ private AuthenticatedHomeserverGeneric _homeserver = null!; + private SvgIdenticonGenerator _identiconGenerator = new(); + protected override async Task OnInitializedAsync() { _homeserver = await RMUStorage.GetCurrentSessionOrNavigate(); if (_homeserver is null) return; @@ -35,6 +38,7 @@ if (UserId == null) { throw new ArgumentNullException(nameof(UserId)); } + User = await _homeserver.GetProfileAsync(UserId); } diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 4c60728..1abe9e7 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -11,4 +11,4 @@ BASE_DIR=`pwd` rm -rf **/bin/Release cd MatrixUtils.Web dotnet publish -c Release -rsync -raP bin/Release/net8.0/publish/wwwroot/ rory.gay:/data/nginx/html_mru/ +rsync --delete -raP bin/Release/net8.0/publish/wwwroot/ rory.gay:/data/nginx/html_mru/ -- cgit 1.4.1