about summary refs log tree commit diff
path: root/MatrixRoomUtils.Web/Pages/Rooms
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixRoomUtils.Web/Pages/Rooms')
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Create.razor338
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Index.razor250
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor267
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Space.razor100
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor144
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor127
-rw-r--r--MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor60
7 files changed, 0 insertions, 1286 deletions
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
deleted file mode 100644

index e477f4c..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor +++ /dev/null
@@ -1,338 +0,0 @@ -@page "/Rooms/Create" -@using System.Text.Json -@using System.Reflection -@using ArcaneLibs.Extensions -@using LibMatrix -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.EventTypes.Spec.State.RoomInfo -@using LibMatrix.Homeservers -@using LibMatrix.Responses -@using MatrixRoomUtils.Web.Classes.RoomCreationTemplates -@* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@ - -<h3>Room Manager - Create Room</h3> - -@* <pre Contenteditable="true" @onkeypress="@JsonChanged" content="JsonString">@JsonString</pre> *@ -<style> - table.table-top-first-tr tr td:first-child { - vertical-align: top; - } - </style> -<table class="table-top-first-tr"> - <tr style="padding-bottom: 16px;"> - <td>Preset:</td> - <td> - @if (Presets is null) { - <p style="color: red;">Presets is null!</p> - } - else { - <InputSelect @bind-Value="@RoomPreset"> - @foreach (var createRoomRequest in Presets) { - <option value="@createRoomRequest.Key">@createRoomRequest.Key</option> - } - </InputSelect> - } - </td> - </tr> - @if (creationEvent is not null) { - <tr> - <td>Room name:</td> - <td> - @if (creationEvent.Name is null) { - <p style="color: red;">creationEvent.Name is null!</p> - } - else { - <FancyTextBox @bind-Value="@creationEvent.Name"></FancyTextBox> - <p>(#<FancyTextBox @bind-Value="@creationEvent.RoomAliasName"></FancyTextBox>:@Homeserver.WhoAmI.UserId.Split(':').Last())</p> - } - </td> - </tr> - <tr> - <td>Room type:</td> - <td> - @if (creationEvent.CreationContentBaseType is null) { - <p style="color: red;">creationEvent._creationContentBaseType is null!</p> - } - else { - <InputSelect @bind-Value="@creationEvent.CreationContentBaseType.Type"> - <option value="">Room</option> - <option value="m.space">Space</option> - </InputSelect> - <FancyTextBox @bind-Value="@creationEvent.CreationContentBaseType.Type"></FancyTextBox> - } - </td> - </tr> - <tr> - <td style="padding-top: 16px;">History visibility:</td> - <td style="padding-top: 16px;"> - <InputSelect @bind-Value="@historyVisibility.HistoryVisibility"> - <option value="invited">Invited</option> - <option value="joined">Joined</option> - <option value="shared">Shared</option> - <option value="world_readable">World readable</option> - </InputSelect> - </td> - </tr> - <tr> - <td>Guest access:</td> - <td> - <ToggleSlider @bind-Value="guestAccessEvent.IsGuestAccessEnabled"> - @(guestAccessEvent.IsGuestAccessEnabled ? "Guests can join" : "Guests cannot join") (@guestAccessEvent.GuestAccess) - </ToggleSlider> - <InputSelect @bind-Value="@guestAccessEvent.GuestAccess"> - <option value="can_join">Can join</option> - <option value="forbidden">Forbidden</option> - </InputSelect> - </td> - </tr> - - <tr> - <td>Room icon:</td> - <td> - <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/> - <div style="display: inline-block; vertical-align: middle;"> - <FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox><br/> - <InputFile OnChange="RoomIconFilePicked"></InputFile> - </div> - </td> - </tr> - <tr> - <td>Permissions:</td> - <details> - <summary>@creationEvent.PowerLevelContentOverride.Users.Count members</summary> - @foreach (var user in creationEvent.PowerLevelContentOverride.Events.Keys) { - var _event = user; - <tr> - <td> - <FancyTextBox Formatter="@GetPermissionFriendlyName" - Value="@_event" - ValueChanged="val => { creationEvent.PowerLevelContentOverride.Events.ChangeKey(_event, val); }"> - </FancyTextBox>: - </td> - <td> - <input type="number" value="@creationEvent.PowerLevelContentOverride.Events[_event]" @oninput="val => { creationEvent.PowerLevelContentOverride.Events[_event] = int.Parse(val.Value.ToString()); }" @onfocusout="() => { creationEvent.PowerLevelContentOverride.Events = creationEvent.PowerLevelContentOverride.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"/> - </td> - </tr> - } - @foreach (var user in creationEvent.PowerLevelContentOverride.Users.Keys) { - var _user = user; - <tr> - <td><FancyTextBox Value="@_user" ValueChanged="val => { creationEvent.PowerLevelContentOverride.Users.ChangeKey(_user, val); creationEvent.PowerLevelContentOverride.Users = creationEvent.PowerLevelContentOverride.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); }"></FancyTextBox>:</td> - <td> - <input type="number" value="@creationEvent.PowerLevelContentOverride.Users[_user]" @oninput="val => { creationEvent.PowerLevelContentOverride.Users[_user] = int.Parse(val.Value.ToString()); }"/> - </td> - </tr> - } - </details> - </tr> - <tr> - <td>Server ACLs:</td> - <td> - @if (serverAcl?.Allow is null) { - <p>No allow rules exist!</p> - <button @onclick="@(() => { serverAcl.Allow = new List<string> { "*" }; })">Create sane defaults</button> - } - else { - <details> - <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Allow.Count) allow rules</summary> - @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@ - </details> - } - @if (serverAcl?.Deny is null) { - <p>No deny rules exist!</p> - <button @onclick="@(() => { serverAcl.Allow = new List<string>(); })">Create sane defaults</button> - } - else { - <details> - <summary>@((creationEvent["m.room.server_acls"].TypedContent as RoomServerACLEventContent).Deny.Count) deny rules</summary> - @* <StringListEditor @bind-Items="@serverAcl.Allow"></StringListEditor> *@ - </details> - } - </td> - </tr> - - <tr> - <td>Invited members:</td> - <td> - <details> - <summary>@creationEvent.InitialState.Count(x => x.Type == "m.room.member") members</summary> - @* <button @onclick="() => { RuntimeCache.LoginSessions.Select(x => x.Value.LoginResponse.UserId).ToList().ForEach(InviteMember); }">Invite all logged in accounts</button> *@ - @foreach (var member in creationEvent.InitialState.Where(x => x.Type == "m.room.member" && x.StateKey != Homeserver.UserId)) { - <UserListItem UserId="@member.StateKey"></UserListItem> - } - </details> - </td> - </tr> - @* Initial states, should remain at bottom *@ - <tr> - <td style="vertical-align: top;">Initial states:</td> - <td> - <details> - - @code - { - private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", "m.room.server_acl" }; - } - - <summary> @creationEvent.InitialState.Count(x => !ImplementedStates.Contains(x.Type)) custom states</summary> - <table> - @foreach (var initialState in creationEvent.InitialState.Where(x => !ImplementedStates.Contains(x.Type))) { - <tr> - <td style="vertical-align: top;"> - @(initialState.Type): - @if (!string.IsNullOrEmpty(initialState.StateKey)) { - <br/> - <span>(@initialState.StateKey)</span> - } - - </td> - <td> - <pre>@JsonSerializer.Serialize(initialState.RawContent, new JsonSerializerOptions { WriteIndented = true })</pre> - </td> - </tr> - } - </table> - </details> - <details> - <summary> @creationEvent.InitialState.Count initial states</summary> - <table> - @foreach (var initialState in creationEvent.InitialState) { - var _state = initialState; - <tr> - <td style="vertical-align: top;"> - <span>@(_state.Type):</span><br/> - <button @onclick="() => { creationEvent.InitialState.Remove(_state); StateHasChanged(); }">Remove</button> - </td> - - <td> - <pre>@JsonSerializer.Serialize(_state.RawContent, new JsonSerializerOptions { WriteIndented = true })</pre> - </td> - </tr> - } - </table> - </details> - </td> - </tr> - } -</table> -<button @onclick="CreateRoom">Create room</button> -<br/> -<ModalWindow Title="Creation JSON"> - <pre> - @creationEvent.ToJson(ignoreNull: true) - </pre> -</ModalWindow> -<ModalWindow Title="Creation JSON (with null values)"> - <pre> - @creationEvent.ToJson() - </pre> -</ModalWindow> - -@if (_matrixException is not null) { - <ModalWindow Title="@("Matrix exception: " + _matrixException.ErrorCode)"> - <pre> - @_matrixException.Message - </pre> - </ModalWindow> -} - -@code { - - private string RoomPreset { - get => Presets.ContainsValue(creationEvent) ? Presets.First(x => x.Value == creationEvent).Key : "Not a preset"; - set { - creationEvent = Presets[value]; - JsonChanged(); - StateHasChanged(); - } - } - - private CreateRoomRequest? creationEvent { get; set; } - - private Dictionary<string, CreateRoomRequest>? Presets { get; set; } = new(); - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - private MatrixException? _matrixException { get; set; } - - private RoomHistoryVisibilityEventContent? historyVisibility => creationEvent?["m.room.history_visibility"].TypedContent as RoomHistoryVisibilityEventContent; - private RoomGuestAccessEventContent? guestAccessEvent => creationEvent?["m.room.guest_access"].TypedContent as RoomGuestAccessEventContent; - private RoomServerACLEventContent? serverAcl => creationEvent?["m.room.server_acls"].TypedContent as RoomServerACLEventContent; - private RoomAvatarEventContent? roomAvatarEvent => creationEvent?["m.room.avatar"].TypedContent as RoomAvatarEventContent; - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - - foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList()) { - Console.WriteLine($"Found room creation template in class: {x.FullName}"); - var instance = (IRoomCreationTemplate)Activator.CreateInstance(x); - Presets[instance.Name] = instance.CreateRoomRequest; - } - Presets = Presets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); - - if (!Presets.ContainsKey("Default")) { - Console.WriteLine($"No default room found in {Presets.Count} presets: {string.Join(", ", Presets.Keys)}"); - } - else RoomPreset = "Default"; - - await base.OnInitializedAsync(); - } - - private void JsonChanged() => Console.WriteLine(creationEvent.ToJson()); - - private async Task RoomIconFilePicked(InputFileChangeEventArgs obj) { - var res = await Homeserver.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType); - Console.WriteLine(res); - (creationEvent["m.room.avatar"].TypedContent as RoomAvatarEventContent).Url = res; - StateHasChanged(); - } - - private async Task CreateRoom() { - Console.WriteLine("Create room"); - Console.WriteLine(creationEvent.ToJson()); - creationEvent.CreationContent.Add("rory.gay.created_using", "Rory&::MatrixRoomUtils (https://mru.rory.gay)"); - try { - var id = await Homeserver.CreateRoom(creationEvent); - } - catch (MatrixException e) { - _matrixException = e; - } - } - - private void InviteMember(string mxid) { - if (!creationEvent.InitialState.Any(x => x.Type == "m.room.member" && x.StateKey == mxid) && Homeserver.UserId != mxid) - creationEvent.InitialState.Add(new StateEvent { - Type = "m.room.member", - StateKey = mxid, - TypedContent = new RoomMemberEventContent { - Membership = "invite", - Reason = "Automatically invited at room creation time." - } - }); - } - - private string GetStateFriendlyName(string key) => key switch { - "m.room.history_visibility" => "History visibility", - "m.room.guest_access" => "Guest access", - "m.room.join_rules" => "Join rules", - "m.room.server_acl" => "Server ACL", - "m.room.avatar" => "Avatar", - _ => key - }; - - private string GetPermissionFriendlyName(string key) => key switch { - "m.reaction" => "Send reaction", - "m.room.avatar" => "Change room icon", - "m.room.canonical_alias" => "Change room alias", - "m.room.encryption" => "Enable encryption", - "m.room.history_visibility" => "Change history visibility", - "m.room.name" => "Change room name", - "m.room.power_levels" => "Change power levels", - "m.room.tombstone" => "Upgrade room", - "m.room.topic" => "Change room topic", - "m.room.pinned_events" => "Pin events", - "m.room.server_acl" => "Change server ACLs", - _ => key - }; - -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor deleted file mode 100644
index 6cabe82..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor +++ /dev/null
@@ -1,250 +0,0 @@ -@page "/Rooms" -@using LibMatrix.Filters -@using LibMatrix.Helpers -@using LibMatrix.Extensions -@using LibMatrix.Responses -@using System.Collections.ObjectModel -@using System.Diagnostics -@using ArcaneLibs.Extensions -@using MatrixRoomUtils.Abstractions -@inject ILogger<Index> logger -<h3>Room list</h3> - -<p>@Status</p> -<p>@Status2</p> - -<LinkButton href="/Rooms/Create">Create new room</LinkButton> - -<RoomList Rooms="Rooms" GlobalProfile="@GlobalProfile" @bind-StillFetching="RenderContents"></RoomList> - -@code { - private ObservableCollection<RoomInfo> Rooms { get; } = new(); - private UserProfileResponse GlobalProfile { get; set; } - - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - private static SyncFilter filter = new() { - AccountData = new SyncFilter.EventFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Presence = new SyncFilter.EventFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Room = new SyncFilter.RoomFilter { - AccountData = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - Ephemeral = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - }, - State = new SyncFilter.RoomFilter.StateFilter { - Types = new List<string> { - "m.room.create", - "m.room.name", - "m.room.avatar", - "org.matrix.mjolnir.shortcode", - "m.room.power_levels", - } - }, - Timeline = new SyncFilter.RoomFilter.StateFilter { - NotTypes = new List<string> { "*" }, - Limit = 1 - } - } - }; - - // private static SyncFilter profileUpdateFilter = new() { - // AccountData = new SyncFilter.EventFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // Presence = new SyncFilter.EventFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // Room = new SyncFilter.RoomFilter { - // AccountData = new SyncFilter.RoomFilter.StateFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // Ephemeral = new SyncFilter.RoomFilter.StateFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // }, - // State = new SyncFilter.RoomFilter.StateFilter { - // Types = new List<string> { - // "m.room.member" - // }, - // Senders = new() - // }, - // Timeline = new SyncFilter.RoomFilter.StateFilter { - // NotTypes = new List<string> { "*" }, - // Limit = 1 - // } - // } - // }; - - private SyncHelper syncHelper; - - // SyncHelper profileSyncHelper; - - protected override async Task OnInitializedAsync() { - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - var rooms = await Homeserver.GetJoinedRooms(); - // SemaphoreSlim _semaphore = new(160, 160); - - var roomTasks = rooms.Select(async room => { - RoomInfo ri; - // await _semaphore.WaitAsync(); - ri = new() { Room = room }; - await Task.WhenAll((filter.Room?.State?.Types ?? []).Select(x => ri.GetStateEvent(x))); - return ri; - }).ToAsyncEnumerable(); - - await foreach (var room in roomTasks) { - Rooms.Add(room); - StateHasChanged(); - // await Task.Delay(50); - // _semaphore.Release(); - } - - if (rooms.Count >= 150) RenderContents = true; - - GlobalProfile = await Homeserver.GetProfileAsync(Homeserver.WhoAmI.UserId); - syncHelper = new SyncHelper(Homeserver, logger) { - Timeout = 30000, - Filter = filter, - MinimumDelay = TimeSpan.FromMilliseconds(5000) - }; - // profileSyncHelper = new SyncHelper(Homeserver, logger) { - // Timeout = 10000, - // Filter = profileUpdateFilter, - // MinimumDelay = TimeSpan.FromMilliseconds(5000) - // }; - // profileUpdateFilter.Room.State.Senders.Add(Homeserver.WhoAmI.UserId); - - RunSyncLoop(syncHelper); - // RunSyncLoop(profileSyncHelper); - RunQueueProcessor(); - - await base.OnInitializedAsync(); - } - - private async Task RunQueueProcessor() { - var renderTimeSw = Stopwatch.StartNew(); - var isInitialSync = true; - while (true) { - try { - while (queue.Count == 0) { - Console.WriteLine("Queue is empty, waiting..."); - await Task.Delay(isInitialSync ? 100 : 2500); - } - - Console.WriteLine($"Queue no longer empty after {renderTimeSw.Elapsed}!"); - - int maxUpdates = 10; - isInitialSync = false; - while (maxUpdates-- > 0 && queue.TryDequeue(out var queueEntry)) { - var (roomId, roomData) = queueEntry; - Console.WriteLine($"Dequeued room {roomId}"); - RoomInfo room; - - if (Rooms.Any(x => x.Room.RoomId == roomId)) { - room = Rooms.First(x => x.Room.RoomId == roomId); - Console.WriteLine($"QueueWorker: {roomId} already known with {room.StateEvents?.Count ?? 0} state events"); - } - else { - Console.WriteLine($"QueueWorker: encountered new room {roomId}!"); - room = new RoomInfo() { - Room = Homeserver.GetRoom(roomId) - }; - Rooms.Add(room); - } - - if (room.StateEvents is null) { - Console.WriteLine($"QueueWorker: {roomId} does not have state events on record?"); - throw new InvalidDataException("Somehow this is null???"); - } - - if (roomData.State?.Events is { Count: > 0 }) - room.StateEvents.MergeStateEventLists(roomData.State.Events); - else { - Console.WriteLine($"QueueWorker: could not merge state for {room.Room.RoomId} as new data contains no state events!"); - } - } - Console.WriteLine($"QueueWorker: {queue.Count} entries left in queue, {maxUpdates} maxUpdates left, RenderContents: {RenderContents}"); - Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue..."; - - RenderContents |= queue.Count == 0; - await Task.Delay(Rooms.Count); - } - catch (Exception e) { - Console.WriteLine("QueueWorker exception: " + e); - } - } - } - - private bool RenderContents { get; set; } = false; - - private string _status; - - public string Status { - get => _status; - set { - _status = value; - StateHasChanged(); - } - } - - private string _status2; - - public string Status2 { - get => _status2; - set { - _status2 = value; - StateHasChanged(); - } - } - - private Queue<KeyValuePair<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>> queue = new(); - - private async Task RunSyncLoop(SyncHelper syncHelper) { - Status = "Initial syncing..."; - Console.WriteLine("starting sync"); - - var syncs = syncHelper.EnumerateSyncAsync(); - await foreach (var sync in syncs) { - Console.WriteLine("trying sync"); - if (sync is null) continue; - - Status = $"Got sync with {sync.Rooms?.Join?.Count ?? 0} room updates, next batch: {sync.NextBatch}!"; - if (sync?.Rooms?.Join != null) - foreach (var joinedRoom in sync.Rooms.Join) - if ( /*joinedRoom.Value.AccountData?.Events?.Count > 0 ||*/ joinedRoom.Value.State?.Events?.Count > 0) { - joinedRoom.Value.State.Events.RemoveAll(x => x.Type == "m.room.member" && x.StateKey != Homeserver.WhoAmI?.UserId); - // We can't trust servers to give us what we ask for, and this ruins performance - // Thanks, Conduit. - joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.Types?.Contains(x.Type) == false); - if (filter.Room?.State?.NotSenders?.Any() ?? false) - joinedRoom.Value.State.Events.RemoveAll(x => filter.Room?.State?.NotSenders?.Contains(x.Sender) ?? false); - - queue.Enqueue(joinedRoom); - } - if (sync.Rooms.Leave is {Count: > 0}) - foreach (var leftRoom in sync.Rooms.Leave) - if (Rooms.Any(x => x.Room.RoomId == leftRoom.Key)) - Rooms.Remove(Rooms.First(x => x.Room.RoomId == leftRoom.Key)); - - Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue... " + - $"{sync?.Rooms?.Join?.Count ?? 0} new updates!"; - - Status2 = $"Next batch: {sync.NextBatch}"; - } - } - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor deleted file mode 100644
index b89d59c..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor +++ /dev/null
@@ -1,267 +0,0 @@ -@page "/Rooms/{RoomId}/Policies" -@using LibMatrix -@using ArcaneLibs.Extensions -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.EventTypes.Spec.State.Policy -@using System.Diagnostics -@using LibMatrix.RoomTypes -@using System.Collections.Frozen -@using System.Reflection -@using ArcaneLibs.Attributes -@using LibMatrix.EventTypes - -@using MatrixRoomUtils.Web.Shared.PolicyEditorComponents - -<h3>Policy list editor - Editing @RoomId</h3> -<hr/> -@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@ -<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton> - -@if (Loading) { - <p>Loading...</p> -} -else if (PolicyEventsByType is not { Count: > 0 }) { - <p>No policies yet</p> -} -else { - @foreach (var (type, value) in PolicyEventsByType) { - <p> - @(GetValidPolicyEventsByType(type).Count) active, - @(GetInvalidPolicyEventsByType(type).Count) invalid - (@value.Count total) - @(GetPolicyTypeName(type).ToLower()) - </p> - } - - @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { - <details> - <summary> - <span> - @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") - </span> - <hr style="margin: revert;"/> - </summary> - <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> - @{ - var policies = GetValidPolicyEventsByType(type); - var invalidPolicies = GetInvalidPolicyEventsByType(type); - // enumerate all properties with friendly name - var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) - .Where(x => x.GetCustomAttribute<TableHideAttribute>() is null) - .ToFrozenSet(); - var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); - } - <thead> - <tr> - @foreach (var name in propNames) { - <th style="border-width: 1px">@name</th> - } - <th style="border-width: 1px">Actions</th> - </tr> - </thead> - <tbody style="border-width: 1px;"> - @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) { - <tr> - @{ - var typedContent = policy.TypedContent!; - var proxySafeProps = typedContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => props.Any(y => y.Name == x.Name)) - .ToFrozenSet(); - Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); - } - @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) { - <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td> - } - <td> - <div style="display: ruby;"> - @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { - <LinkButton OnClick="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton> - <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton> - @if (policy.IsLegacyType) { - <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton> - } - } - </div> - </td> - </tr> - } - </tbody> - </table> - <details> - <summary> - <u> - @("Invalid " + GetPolicyTypeName(type).ToLower()) - </u> - </summary> - <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> - <thead> - <tr> - <th style="border-width: 1px">State key</th> - <th style="border-width: 1px">Json contents</th> - </tr> - </thead> - <tbody> - @foreach (var policy in invalidPolicies) { - <tr> - <td>@policy.StateKey</td> - <td> - <pre>@policy.RawContent.ToJson(true, false)</pre> - </td> - </tr> - } - </tbody> - </table> - </details> - </details> - } -} - -@if (CurrentlyEditingEvent is not null) { - <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal> -} - -@code { - -#if DEBUG - private const bool Debug = true; -#else - private const bool Debug = false; -#endif - - private bool Loading { get; set; } = true; - //get room list - // - sync withroom list filter - // Type = support.feline.msc3784 - //support.feline.policy.lists.msc.v1 - - [Parameter] - public string RoomId { get; set; } = null!; - - private bool _enableAvatars; - private StateEventResponse? _currentlyEditingEvent; - - // static readonly Dictionary<string, string?> Avatars = new(); - // static readonly Dictionary<string, RemoteHomeserver> Servers = new(); - - // private static List<StateEventResponse> PolicyEvents { get; set; } = new(); - private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new(); - - private StateEventResponse? CurrentlyEditingEvent { - get => _currentlyEditingEvent; - set { - _currentlyEditingEvent = value; - StateHasChanged(); - } - } - - // public bool EnableAvatars { - // get => _enableAvatars; - // set { - // _enableAvatars = value; - // if (value) GetAllAvatars(); - // } - // } - - private AuthenticatedHomeserverGeneric Homeserver { get; set; } - private GenericRoom Room { get; set; } - private RoomPowerLevelEventContent PowerLevels { get; set; } - - protected override async Task OnInitializedAsync() { - var sw = Stopwatch.StartNew(); - await base.OnInitializedAsync(); - Homeserver = (await MRUStorage.GetCurrentSessionOrNavigate())!; - if (Homeserver is null) return; - Room = Homeserver.GetRoom(RoomId!); - PowerLevels = (await Room.GetPowerLevelsAsync())!; - await LoadStatesAsync(); - Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); - } - - private async Task LoadStatesAsync() { - Loading = true; - var states = Room.GetFullStateAsync(); - PolicyEventsByType.Clear(); - await foreach (var state in states) { - if (state is null) continue; - if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; - if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new()); - PolicyEventsByType[state.MappedType].Add(state); - } - - Loading = false; - StateHasChanged(); - } - - // private async Task GetAllAvatars() { - // // if (!_enableAvatars) return; - // Console.WriteLine("Getting avatars..."); - // var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue<string>()).Where(x => x.Contains(':') && !x.Contains("*")).ToList(); - // Console.WriteLine($"Got {users.Count} users!"); - // var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList()); - // Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!"); - // var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable(); - // await foreach (var server in homeserverTasks) { - // if (server is null) continue; - // var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList(); - // await Task.WhenAll(profileTasks); - // profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } }); - // foreach (var profile in profileTasks.Select(x => x.Result!.Value)) { - // // if (profile is null) continue; - // if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) { - // var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl); - // Avatars.TryAdd(profile.Key, url); - // } - // else Avatars.TryAdd(profile.Key, null); - // } - // - // StateHasChanged(); - // } - // } - // - // private async Task<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) { - // try { - // return new KeyValuePair<string, UserProfileResponse>(mxid, await server.GetProfileAsync(mxid)); - // } - // catch { - // return null; - // } - // } - - private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; - - private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); - - private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); - - private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() - ?? type.GetCustomAttributes<MatrixEventAttribute>() - .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.EventName))?.EventName; - - private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name; - - private async Task RemovePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, new { }); - PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); - await LoadStatesAsync(); - } - - private async Task UpdatePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent); - await LoadStatesAsync(); - } - - private async Task UpgradePolicyAsync(StateEventResponse policyEvent) { - policyEvent.RawContent["upgraded_from_type"] = policyEvent.Type; - await LoadStatesAsync(); - } - - private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); - - // event types, unnamed - private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes - .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); - -} \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Space.razor b/MatrixRoomUtils.Web/Pages/Rooms/Space.razor deleted file mode 100644
index ce94d01..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Space.razor +++ /dev/null
@@ -1,100 +0,0 @@ -@page "/Rooms/{RoomId}/Space" -@using LibMatrix.RoomTypes -@using ArcaneLibs.Extensions -@using LibMatrix -<h3>Room manager - Viewing Space</h3> - -<button onclick="@JoinAllRooms">Join all rooms</button> -@foreach (var room in Rooms) { - <RoomListItem Room="room" ShowOwnProfile="true"></RoomListItem> -} - - -<br/> -<details style="background: #0002;"> - <summary style="background: #fff1;">State list</summary> - @foreach (var stateEvent in States.OrderBy(x => x.StateKey).ThenBy(x => x.Type)) { - <p>@stateEvent.StateKey/@stateEvent.Type:</p> - <pre>@stateEvent.RawContent.ToJson()</pre> - } -</details> - -@code { - - [Parameter] - public string RoomId { get; set; } = "invalid!!!!!!"; - - private GenericRoom? Room { get; set; } - - private StateEventResponse[] States { get; set; } = Array.Empty<StateEventResponse>(); - private List<GenericRoom> Rooms { get; } = new(); - private List<string> ServersInSpace { get; } = new(); - - protected override async Task OnInitializedAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - Room = hs.GetRoom(RoomId.Replace('~', '.')); - - var state = Room.GetFullStateAsync(); - await foreach (var stateEvent in state) { - switch (stateEvent.Type) { - case "m.space.child": { - var roomId = stateEvent.StateKey; - var room = hs.GetRoom(roomId); - if (room is not null) { - Rooms.Add(room); - } - break; - } - case "m.room.member": { - var serverName = stateEvent.StateKey.Split(':').Last(); - if (!ServersInSpace.Contains(serverName)) { - ServersInSpace.Add(serverName); - } - break; - } - } - } - await base.OnInitializedAsync(); - - // var state = await Room.GetStateAsync(""); - // if (state is not null) { - // // Console.WriteLine(state.Value.ToJson()); - // States = state.Value.Deserialize<StateEventResponse[]>()!; - // - // foreach (var stateEvent in States) { - // if (stateEvent.Type == "m.space.child") { - // // if (stateEvent.Content.ToJson().Length < 5) return; - // var roomId = stateEvent.StateKey; - // var room = hs.GetRoom(roomId); - // if (room is not null) { - // Rooms.Add(room); - // } - // } - // else if (stateEvent.Type == "m.room.member") { - // var serverName = stateEvent.StateKey.Split(':').Last(); - // if (!ServersInSpace.Contains(serverName)) { - // ServersInSpace.Add(serverName); - // } - // } - // } - - // if(state.Value.TryGetProperty("Type", out var Type)) - // { - // } - // else - // { - // //this is fine, apprently... - // //Console.WriteLine($"Room {room.RoomId} has no Content.Type in m.room.create!"); - // } - - // await base.OnInitializedAsync(); - } - - private async Task JoinAllRooms() { - List<Task<RoomIdResponse>> tasks = Rooms.Select(room => room.JoinAsync(ServersInSpace.ToArray())).ToList(); - await Task.WhenAll(tasks); - } - -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor deleted file mode 100644
index 0e9d4f6..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor +++ /dev/null
@@ -1,144 +0,0 @@ -@page "/Rooms/{RoomId}/State/Edit" -@using ArcaneLibs.Extensions -@using LibMatrix -@inject ILocalStorageService LocalStorage -@inject NavigationManager NavigationManager -<h3>Room state editor - Editing @RoomId</h3> -<hr/> - -<p>@status</p> - -<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events -<br/> -<InputSelect @bind-Value="shownStateKey"> - <option value="">-- State key --</option> - @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey != "").Select(x => x.StateKey).Distinct().OrderBy(x => x)) { - <option value="@stateEvent">@stateEvent</option> - Console.WriteLine(stateEvent); - } -</InputSelect> -<br/> -<InputSelect @bind-Value="shownType"> - <option value="">-- Type --</option> - @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey != shownStateKey).Select(x => x.Type).Distinct().OrderBy(x => x)) { - <option value="@stateEvent">@stateEvent</option> - } -</InputSelect> -<br/> - -<textarea @bind="shownEventJson" style="width: 100%; height: fit-Content;"></textarea> - -<LogView></LogView> - -@code { - //get room list - // - sync withroom list filter - // Type = support.feline.msc3784 - //support.feline.policy.lists.msc.v1 - - [Parameter] - public string? RoomId { get; set; } - - public List<StateEventResponse> FilteredEvents { get; set; } = new(); - public List<StateEventResponse> Events { get; set; } = new(); - public string status = ""; - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - RoomId = RoomId.Replace('~', '.'); - await LoadStatesAsync(); - Console.WriteLine("Policy list editor initialized!"); - } - - private DateTime _lastUpdate = DateTime.Now; - - private async Task LoadStatesAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - - var StateLoaded = 0; - var response = (hs.GetRoom(RoomId)).GetFullStateAsync(); - await foreach (var _ev in response) { - // var e = new StateEventResponse { - // Type = _ev.Type, - // StateKey = _ev.StateKey, - // OriginServerTs = _ev.OriginServerTs, - // Content = _ev.Content - // }; - Events.Add(_ev); - if (string.IsNullOrEmpty(_ev.StateKey)) { - FilteredEvents.Add(_ev); - } - StateLoaded++; - - if (!((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)) continue; - _lastUpdate = DateTime.Now; - status = $"Loaded {StateLoaded} state events"; - StateHasChanged(); - await Task.Delay(0); - } - - StateHasChanged(); - } - - private async Task RebuildFilteredData() { - status = "Rebuilding filtered data..."; - StateHasChanged(); - await Task.Delay(1); - var _FilteredEvents = Events; - if (!ShowMembershipEvents) - _FilteredEvents = _FilteredEvents.Where(x => x.Type != "m.room.member").ToList(); - - status = "Done, rerendering!"; - StateHasChanged(); - await Task.Delay(1); - FilteredEvents = _FilteredEvents; - - if (_shownType is not null) - shownEventJson = _FilteredEvents.First(x => x.Type == _shownType).RawContent.ToJson(indent: true, ignoreNull: true); - - StateHasChanged(); - } - - public struct PreRenderedStateEvent { - public string content { get; set; } - public long origin_server_ts { get; set; } - public string state_key { get; set; } - public string type { get; set; } - // public string Sender { get; set; } - // public string EventId { get; set; } - // public string UserId { get; set; } - // public string ReplacesState { get; set; } - } - - public bool ShowMembershipEvents { - get => _showMembershipEvents; - set { - _showMembershipEvents = value; - RebuildFilteredData(); - } - } - - private bool _showMembershipEvents; - private string _shownStateKey; - private string _shownType; - - private string shownStateKey { - get => _shownStateKey; - set { - _shownStateKey = value; - RebuildFilteredData(); - } - } - - private string shownType { - get => _shownType; - set { - _shownType = value; - RebuildFilteredData(); - } - } - - private string shownEventJson { get; set; } -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor deleted file mode 100644
index 2d0e0b0..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor +++ /dev/null
@@ -1,127 +0,0 @@ -@page "/Rooms/{RoomId}/State/View" -@using ArcaneLibs.Extensions -@using LibMatrix -@inject ILocalStorageService LocalStorage -@inject NavigationManager NavigationManager -<h3>Room state viewer - Viewing @RoomId</h3> -<hr/> - -<p>@status</p> - -<input type="checkbox" id="showAll" @bind="ShowMembershipEvents"/> Show member events - -<table class="table table-striped table-hover" style="width: fit-Content;"> - <thead> - <tr> - <th scope="col">Type</th> - <th scope="col">Content</th> - </tr> - </thead> - <tbody> - @foreach (var stateEvent in FilteredEvents.Where(x => x.StateKey == "").OrderBy(x => x.OriginServerTs)) { - <tr> - <td>@stateEvent.Type</td> - <td style="max-width: fit-Content;"> - <pre>@stateEvent.RawContent.ToJson()</pre> - </td> - </tr> - } - </tbody> -</table> - -@foreach (var group in FilteredEvents.GroupBy(x => x.StateKey).OrderBy(x => x.Key).Where(x => x.Key != "")) { - <details> - <summary>@group.Key</summary> - <table class="table table-striped table-hover" style="width: fit-Content;"> - <thead> - <tr> - <th scope="col">Type</th> - <th scope="col">Content</th> - </tr> - </thead> - <tbody> - @foreach (var stateEvent in group.OrderBy(x => x.OriginServerTs)) { - <tr> - <td>@stateEvent.Type</td> - <td style="max-width: fit-Content;"> - <pre>@stateEvent.RawContent.ToJson()</pre> - </td> - </tr> - } - </tbody> - </table> - </details> -} - -<LogView></LogView> - -@code { - //get room list - // - sync withroom list filter - // Type = support.feline.msc3784 - //support.feline.policy.lists.msc.v1 - - [Parameter] - public string? RoomId { get; set; } - - public List<StateEventResponse> FilteredEvents { get; set; } = new(); - public List<StateEventResponse> Events { get; set; } = new(); - public string status = ""; - - protected override async Task OnInitializedAsync() { - await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - await LoadStatesAsync(); - Console.WriteLine("Policy list editor initialized!"); - } - - private DateTime _lastUpdate = DateTime.Now; - - private async Task LoadStatesAsync() { - var StateLoaded = 0; - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - var response = (hs.GetRoom(RoomId)).GetFullStateAsync(); - await foreach (var _ev in response) { - Events.Add(_ev); - if (string.IsNullOrEmpty(_ev.StateKey)) { - FilteredEvents.Add(_ev); - } - StateLoaded++; - - if (!((DateTime.Now - _lastUpdate).TotalMilliseconds > 100)) continue; - _lastUpdate = DateTime.Now; - status = $"Loaded {StateLoaded} state events"; - StateHasChanged(); - await Task.Delay(0); - } - - StateHasChanged(); - } - - private async Task RebuildFilteredData() { - status = "Rebuilding filtered data..."; - StateHasChanged(); - await Task.Delay(1); - var _FilteredEvents = Events; - if (!ShowMembershipEvents) - _FilteredEvents = _FilteredEvents.Where(x => x.Type != "m.room.member").ToList(); - - status = "Done, rerendering!"; - StateHasChanged(); - await Task.Delay(1); - FilteredEvents = _FilteredEvents; - StateHasChanged(); - } - - public bool ShowMembershipEvents { - get => _showMembershipEvents; - set { - _showMembershipEvents = value; - RebuildFilteredData(); - } - } - - private bool _showMembershipEvents; -} diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor deleted file mode 100644
index 01bf555..0000000 --- a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor +++ /dev/null
@@ -1,60 +0,0 @@ -@page "/Rooms/{RoomId}/Timeline" -@using MatrixRoomUtils.Web.Shared.TimelineComponents -@using LibMatrix -@using LibMatrix.EventTypes.Spec -@using LibMatrix.EventTypes.Spec.State -@using LibMatrix.Homeservers -<h3>RoomManagerTimeline</h3> -<hr/> -<p>Loaded @Events.Count events...</p> - -@foreach (var evt in Events) { - <div type="@evt.Type" key="@evt.StateKey" itemid="@evt.EventId"> - <DynamicComponent Type="@ComponentType(evt)" - Parameters="@(new Dictionary<string, object> { { "Event", evt }, { "Events", Events }, { "Homeserver", Homeserver!} })"> - </DynamicComponent> - </div> -} - -@code { - - [Parameter] - public string RoomId { get; set; } - - private List<MessagesResponse> Messages { get; } = new(); - private List<StateEventResponse> Events { get; } = new(); - - private AuthenticatedHomeserverGeneric? Homeserver { get; set; } - - protected override async Task OnInitializedAsync() { - Console.WriteLine("RoomId: " + RoomId); - Homeserver = await MRUStorage.GetCurrentSessionOrNavigate(); - if (Homeserver is null) return; - var room = Homeserver.GetRoom(RoomId); - MessagesResponse? msgs = null; - do { - msgs = await room.GetMessagesAsync(limit: 1000, from: msgs?.End, dir: "b"); - Messages.Add(msgs); - Console.WriteLine($"Got {msgs.Chunk.Count} messages"); - msgs.Chunk.Reverse(); - Events.InsertRange(0, msgs.Chunk); - } while (msgs.End is not null); - - - await base.OnInitializedAsync(); - } - - private StateEventResponse GetProfileEventBefore(StateEventResponse Event) => Events.TakeWhile(x => x != Event).Last(e => e.Type == "m.room.member" && e.StateKey == Event.Sender); - - private Type ComponentType(StateEvent Event) => Event.TypedContent switch { - RoomCanonicalAliasEventContent => typeof(TimelineCanonicalAliasItem), - RoomHistoryVisibilityEventContent => typeof(TimelineHistoryVisibilityItem), - RoomTopicEventContent => typeof(TimelineRoomTopicItem), - RoomMemberEventContent => typeof(TimelineMemberItem), - RoomMessageEventContent => typeof(TimelineMessageItem), - RoomCreateEventContent => typeof(TimelineRoomCreateItem), - RoomNameEventContent => typeof(TimelineRoomNameItem), - _ => typeof(TimelineUnknownItem) - }; - -}