diff --git a/ArcaneLibs b/ArcaneLibs
-Subproject 8f3dce2beefb1117816c89d8f2a638e76306a9a
+Subproject f99ce19767f85d6d2f05919b91267cc140a792a
diff --git a/LibMatrix b/LibMatrix
-Subproject b75135d8cdb702423d693558ffaec3f025264b9
+Subproject aaa3204704b677b59b60dd2719080ead5a0c127
diff --git a/MatrixRoomUtils.LibDMSpace/MatrixRoomUtils.LibDMSpace.csproj b/MatrixRoomUtils.LibDMSpace/MatrixRoomUtils.LibDMSpace.csproj
index 70b4ffc..f21b154 100644
--- a/MatrixRoomUtils.LibDMSpace/MatrixRoomUtils.LibDMSpace.csproj
+++ b/MatrixRoomUtils.LibDMSpace/MatrixRoomUtils.LibDMSpace.csproj
@@ -2,9 +2,11 @@
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
- <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <LinkIncremental>true</LinkIncremental>
<LangVersion>preview</LangVersion>
+ <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
diff --git a/MatrixRoomUtils.LibDMSpace/StateEvents/DMRoomInfo.cs b/MatrixRoomUtils.LibDMSpace/StateEvents/DMRoomInfo.cs
index b88f06a..de5cb26 100644
--- a/MatrixRoomUtils.LibDMSpace/StateEvents/DMRoomInfo.cs
+++ b/MatrixRoomUtils.LibDMSpace/StateEvents/DMRoomInfo.cs
@@ -5,7 +5,7 @@ using LibMatrix.Interfaces;
namespace MatrixRoomUtils.LibDMSpace.StateEvents;
[MatrixEvent(EventName = EventId)]
-public class DMRoomInfo : EventContent {
+public class DMRoomInfo : TimelineEventContent {
public const string EventId = "gay.rory.dm_room_info";
[JsonPropertyName("remote_users")]
public List<string> RemoteUsers { get; set; }
diff --git a/MatrixRoomUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs b/MatrixRoomUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs
index 7824324..80eeef9 100644
--- a/MatrixRoomUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs
+++ b/MatrixRoomUtils.LibDMSpace/StateEvents/DMSpaceInfo.cs
@@ -5,7 +5,7 @@ using LibMatrix.Interfaces;
namespace MatrixRoomUtils.LibDMSpace.StateEvents;
[MatrixEvent(EventName = EventId)]
-public class DMSpaceInfo : EventContent {
+public class DMSpaceInfo : TimelineEventContent {
public const string EventId = "gay.rory.dm_space_info";
[JsonPropertyName("is_layered")]
diff --git a/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs b/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs
index b6836c8..2c3b9ce 100644
--- a/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs
+++ b/MatrixRoomUtils.Web/Classes/MRUStorageWrapper.cs
@@ -43,11 +43,11 @@ public class MRUStorageWrapper(TieredStorageService storageService, HomeserverPr
return null;
}
- return await homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken);
+ return await homeserverProviderService.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy);
}
public async Task<AuthenticatedHomeserverGeneric?> GetSession(UserAuth userAuth) {
- return await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken);
+ return await homeserverProviderService.GetAuthenticatedWithToken(userAuth.Homeserver, userAuth.AccessToken, userAuth.Proxy);
}
public async Task<AuthenticatedHomeserverGeneric?> GetCurrentSessionOrNavigate() {
diff --git a/MatrixRoomUtils.Web/Classes/RoomInfo.cs b/MatrixRoomUtils.Web/Classes/RoomInfo.cs
index a2fa6f5..37973a0 100644
--- a/MatrixRoomUtils.Web/Classes/RoomInfo.cs
+++ b/MatrixRoomUtils.Web/Classes/RoomInfo.cs
@@ -25,7 +25,18 @@ public class RoomInfo : NotifyPropertyChanged {
@event.RawContent = await Room.GetStateAsync<JsonObject>(type, stateKey);
}
catch (MatrixException e) {
- if (e is { ErrorCode: "M_NOT_FOUND" }) @event.RawContent = default!;
+ if (e is { ErrorCode: "M_NOT_FOUND" }) {
+ if (type == "m.room.name")
+ @event = new() {
+ Type = type,
+ StateKey = stateKey,
+ TypedContent = new RoomNameEventContent() {
+ Name = await Room.GetNameOrFallbackAsync()
+ }
+ };
+ else
+ @event.RawContent = default!;
+ }
else throw;
}
@@ -60,7 +71,7 @@ public class RoomInfo : NotifyPropertyChanged {
private string? _roomName;
private RoomCreateEventContent? _creationEventContent;
private string? _roomCreator;
-
+
public string? DefaultRoomName { get; set; }
public RoomInfo() {
diff --git a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
index 543f8db..5d5568f 100644
--- a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
+++ b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
@@ -4,9 +4,11 @@
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
- <UseBlazorWebAssembly>true</UseBlazorWebAssembly>
<LinkIncremental>true</LinkIncremental>
<LangVersion>preview</LangVersion>
+ <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
+
+ <UseBlazorWebAssembly>true</UseBlazorWebAssembly>
</PropertyGroup>
<ItemGroup>
diff --git a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor
index 4b2dc4f..94c51b2 100644
--- a/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor
+++ b/MatrixRoomUtils.Web/Pages/Dev/DevUtilities.razor
@@ -16,7 +16,7 @@ else {
<summary>Room List</summary>
@foreach (var room in Rooms) {
<a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@room.Replace('.', '~')">
- <RoomListItem RoomId="@room"></RoomListItem>
+ <RoomListItem RoomInfo="@(new RoomInfo() { Room = hs.GetRoom(room) })" LoadData="true"></RoomListItem>
</a>
}
</details>
@@ -37,10 +37,11 @@ else {
@code {
public List<string> Rooms { get; set; } = new();
+ public AuthenticatedHomeserverGeneric? hs { get; set; }
protected override async Task OnInitializedAsync() {
await base.OnInitializedAsync();
- var hs = await MRUStorage.GetCurrentSessionOrNavigate();
+ hs = await MRUStorage.GetCurrentSessionOrNavigate();
if (hs == null) return;
Rooms = (await hs.GetJoinedRooms()).Select(x => x.RoomId).ToList();
Console.WriteLine("Fetched joined rooms!");
@@ -76,4 +77,4 @@ else {
StateHasChanged();
}
-}
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor
index 59ce70f..c605e7a 100644
--- a/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor
+++ b/MatrixRoomUtils.Web/Pages/HSAdmin/HSAdmin.razor
@@ -1,20 +1,34 @@
@page "/HSAdmin"
@using LibMatrix.Homeservers
+@using ArcaneLibs.Extensions
<h3>Homeserver Admininistration</h3>
<hr/>
-<h4>Synapse tools</h4>
-<hr/>
-<a href="/HSAdmin/RoomQuery">Query rooms</a>
+@if (Homeserver is null) {
+ <p>Homeserver is null...</p>
+}
+else {
+ @if (Homeserver is AuthenticatedHomeserverSynapse) {
+ <h4>Synapse tools</h4>
+ <hr/>
+ <a href="/HSAdmin/RoomQuery">Query rooms</a>
+ }
+ else {
+ <p>Homeserver type @Homeserver.GetType().Name does not have any administration tools in MRU.</p>
+ <p>Server info:</p>
+ <pre>@ServerVersionResponse?.ToJson(ignoreNull: true)</pre>
+ }
+}
@code {
public AuthenticatedHomeserverGeneric? Homeserver { get; set; }
+ public ServerVersionResponse? ServerVersionResponse { get; set; }
protected override async Task OnInitializedAsync() {
Homeserver = await MRUStorage.GetCurrentSessionOrNavigate();
if (Homeserver is null) return;
+ ServerVersionResponse = await Homeserver.GetServerVersionAsync();
await base.OnInitializedAsync();
}
-
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/Index.razor b/MatrixRoomUtils.Web/Pages/Index.razor
index 74dd651..804fde3 100644
--- a/MatrixRoomUtils.Web/Pages/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Index.razor
@@ -3,6 +3,7 @@
@using LibMatrix
@using LibMatrix.Homeservers
@using ArcaneLibs.Extensions
+@using MatrixRoomUtils.Web.Pages.Dev
<PageTitle>Index</PageTitle>
@@ -28,13 +29,18 @@ Small collection of tools to do not-so-everyday things.
</p>
<span style="display: inline-block; width: 128px;">@__auth.UserInfo.RoomCount rooms</span>
- <span style="color: #888888">@__auth.ServerVersion.Server.Name @__auth.ServerVersion.Server.Version</span>
+ <a style="color: #888888" href="@("/ServerInfo/"+__auth.Homeserver.ServerName+"/")">@__auth.ServerVersion.Server.Name @__auth.ServerVersion.Server.Version</a>
@if (_auth.Proxy != null) {
<span class="badge badge-info"> (proxied via @_auth.Proxy)</span>
}
else {
<p>Not proxied</p>
}
+ @if (DEBUG) {
+ <p>T=@__auth.Homeserver.GetType().FullName</p>
+ <p>D=@__auth.Homeserver.WhoAmI.DeviceId</p>
+ <p>U=@__auth.Homeserver.WhoAmI.UserId</p>
+ }
</td>
<td>
<p>
@@ -51,10 +57,17 @@ Small collection of tools to do not-so-everyday things.
@code
{
+#if DEBUG
+ bool DEBUG = true;
+#else
+ bool DEBUG = false;
+#endif
+
private class AuthInfo {
public UserAuth UserAuth { get; set; }
public UserInfo UserInfo { get; set; }
public ServerVersionResponse ServerVersion { get; set; }
+ public AuthenticatedHomeserverGeneric Homeserver { get; set; }
}
// private Dictionary<UserAuth, UserInfo> _users = new();
@@ -69,7 +82,7 @@ Small collection of tools to do not-so-everyday things.
UserInfo userInfo = new();
AuthenticatedHomeserverGeneric hs;
try {
- hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken);
+ hs = await hsProvider.GetAuthenticatedWithToken(token.Homeserver, token.AccessToken, token.Proxy);
}
catch (MatrixException e) {
if (e.ErrorCode == "M_UNKNOWN_TOKEN") {
@@ -88,7 +101,8 @@ Small collection of tools to do not-so-everyday things.
_auth.Add(new() {
UserInfo = userInfo,
UserAuth = token,
- ServerVersion = await hs.GetServerVersionAsync()
+ ServerVersion = await hs.GetServerVersionAsync(),
+ Homeserver = hs
});
// StateHasChanged();
});
@@ -105,7 +119,7 @@ Small collection of tools to do not-so-everyday things.
private async Task RemoveUser(UserAuth auth, bool logout = false) {
try {
if (logout) {
- await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken)).Logout();
+ await (await hsProvider.GetAuthenticatedWithToken(auth.Homeserver, auth.AccessToken, auth.Proxy)).Logout();
}
}
catch (Exception e) {
diff --git a/MatrixRoomUtils.Web/Pages/LoginPage.razor b/MatrixRoomUtils.Web/Pages/LoginPage.razor
index 1b466c9..c926a93 100644
--- a/MatrixRoomUtils.Web/Pages/LoginPage.razor
+++ b/MatrixRoomUtils.Web/Pages/LoginPage.razor
@@ -7,15 +7,15 @@
<span>
<span>@@</span><!--
- --><FancyTextBox @bind-Value="@newRecordInput.username"></FancyTextBox><!--
+ --><FancyTextBox @bind-Value="@newRecordInput.Username"></FancyTextBox><!--
--><span>:</span><!--
- --><FancyTextBox @bind-Value="@newRecordInput.homeserver"></FancyTextBox>
+ --><FancyTextBox @bind-Value="@newRecordInput.Homeserver"></FancyTextBox>
via
- <FancyTextBox @bind-Value="@newRecordInput.password" IsPassword="true"></FancyTextBox>
+ <FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox>
</span>
<span style="display: block;">
<label>Password:</label>
- <FancyTextBox @bind-Value="@newRecordInput.password" IsPassword="true"></FancyTextBox>
+ <FancyTextBox @bind-Value="@newRecordInput.Password" IsPassword="true"></FancyTextBox>
</span>
<button @onclick="AddRecord">Add account to queue</button>
<br/>
@@ -34,18 +34,18 @@
</thead>
@foreach (var record in records) {
var r = record;
- <tr style="background-color: @(LoggedInSessions.Any(x => x.UserId == $"@{r.username}:{r.homeserver}" && x.Proxy == r.proxy) ? "green" : "unset")">
+ <tr style="background-color: @(LoggedInSessions.Any(x => x.UserId == $"@{r.Username}:{r.Homeserver}" && x.Proxy == r.Proxy) ? "green" : "unset")">
<td style="border-width: 1px;">
- <FancyTextBox @bind-Value="@r.homeserver"></FancyTextBox>
+ <FancyTextBox @bind-Value="@r.Username"></FancyTextBox>
</td>
<td style="border-width: 1px;">
- <FancyTextBox @bind-Value="@r.username"></FancyTextBox>
+ <FancyTextBox @bind-Value="@r.Homeserver"></FancyTextBox>
</td>
<td style="border-width: 1px;">
- <FancyTextBox @bind-Value="@r.password" IsPassword="true"></FancyTextBox>
+ <FancyTextBox @bind-Value="@r.Password" IsPassword="true"></FancyTextBox>
</td>
<td style="border-width: 1px;">
- <FancyTextBox @bind-Value="@r.proxy"></FancyTextBox>
+ <FancyTextBox @bind-Value="@r.Proxy"></FancyTextBox>
</td>
<td>
<a role="button" @onclick="() => records.Remove(r)">Remove</a>
@@ -59,21 +59,20 @@
<LogView></LogView>
@code {
- readonly List<(string homeserver, string username, string password, string? proxy)> records = new();
- (string homeserver, string username, string password, string? proxy) newRecordInput = ("", "", "", null);
+ readonly List<LoginStruct> records = new();
+ private LoginStruct newRecordInput = new();
List<UserAuth>? LoggedInSessions { get; set; } = new();
async Task Login() {
var loginTasks = records.Select(async record => {
- var (homeserver, username, password, proxy) = record;
- if (LoggedInSessions.Any(x => x.UserId == $"@{username}:{homeserver}" && x.Proxy == proxy)) return;
+ if (LoggedInSessions.Any(x => x.UserId == $"@{record.Username}:{record.Homeserver}" && x.Proxy == record.Proxy)) return;
try {
- var result = new UserAuth(await hsProvider.Login(homeserver, username, password, proxy)) {
- Proxy = proxy
+ var result = new UserAuth(await hsProvider.Login(record.Homeserver, record.Username, record.Password, record.Proxy)) {
+ Proxy = record.Proxy
};
if (result == null) {
- Console.WriteLine($"Failed to login to {homeserver} as {username}!");
+ Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!");
return;
}
Console.WriteLine($"Obtained access token for {result.UserId}!");
@@ -82,7 +81,7 @@
LoggedInSessions = await MRUStorage.GetAllTokens();
}
catch (Exception e) {
- Console.WriteLine($"Failed to login to {homeserver} as {username}!");
+ Console.WriteLine($"Failed to login to {record.Homeserver} as {record.Username}!");
Console.WriteLine(e);
}
StateHasChanged();
@@ -104,14 +103,21 @@
if (parts.Length < 3)
continue;
string? via = parts.Length > 3 ? parts[3] : null;
- records.Add((parts[0], parts[1], parts[2], via));
+ records.Add(new() { Homeserver = parts[0], Username = parts[1], Password = parts[2], Proxy = via });
}
}
private async Task AddRecord() {
LoggedInSessions = await MRUStorage.GetAllTokens();
records.Add(newRecordInput);
- newRecordInput = ("", "", "", null);
+ newRecordInput = new();
}
-}
+ private class LoginStruct {
+ public string? Homeserver { get; set; } = "";
+ public string? Username { get; set; } = "";
+ public string? Password { get; set; } = "";
+ public string? Proxy { get; set; }
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor
new file mode 100644
index 0000000..02dfe44
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/ModerationUtilities/UserRoomHistory.razor
@@ -0,0 +1,113 @@
+@page "/UserRoomHistory/{UserId}"
+@using LibMatrix.Homeservers
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.RoomTypes
+@using ArcaneLibs.Extensions
+<h3>UserRoomHistory</h3>
+
+<span>Enter mxid: </span>
+<FancyTextBox @bind-Value="@UserId"></FancyTextBox>
+
+@if (string.IsNullOrWhiteSpace(UserId)) {
+ <p>UserId is null!</p>
+}
+else {
+ <p>Checked @checkedRooms.Count so far...</p>
+ @if (currentHs is not null) {
+ <p>Checking rooms from @currentHs.UserId's perspective</p>
+ }
+ else if (checkedRooms.Count > 1) {
+ <p>Done!</p>
+ }
+ @foreach (var (state, rooms) in matchingStates) {
+ <u>@state</u>
+ <br/>
+ @foreach (var roomInfo in rooms) {
+ <RoomListItem RoomInfo="roomInfo" LoadData="true"></RoomListItem>
+ }
+ }
+}
+
+@code {
+ private string? _userId;
+
+ [Parameter]
+ public string? UserId {
+ get => _userId;
+ set {
+ _userId = value;
+ FindMember(value);
+ }
+ }
+
+ private List<AuthenticatedHomeserverGeneric> hss = new();
+ private AuthenticatedHomeserverGeneric? currentHs { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ var hs = await MRUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+ var sessions = await MRUStorage.GetAllTokens();
+ foreach (var userAuth in sessions) {
+ var session = await MRUStorage.GetSession(userAuth);
+ if (session is not null) {
+ hss.Add(session);
+ StateHasChanged();
+ }
+ }
+
+ StateHasChanged();
+ Console.WriteLine("Rerendered!");
+ await base.OnInitializedAsync();
+ if (!string.IsNullOrWhiteSpace(UserId)) FindMember(UserId);
+ }
+
+ public Dictionary<string, List<RoomInfo>> matchingStates = new();
+ public List<string> checkedRooms = new();
+ private SemaphoreSlim _semaphoreSlim = new(1, 1);
+
+ public async Task FindMember(string mxid) {
+ await _semaphoreSlim.WaitAsync();
+ if (mxid != UserId) {
+ _semaphoreSlim.Release();
+ return; //abort if changed
+ }
+ matchingStates.Clear();
+ foreach (var homeserver in hss) {
+ currentHs = homeserver;
+ var rooms = await homeserver.GetJoinedRooms();
+ rooms.RemoveAll(x => checkedRooms.Contains(x.RoomId));
+ checkedRooms.AddRange(rooms.Select(x => x.RoomId));
+ var tasks = rooms.Select(x => GetMembershipAsync(x, mxid)).ToAsyncEnumerable();
+ await foreach (var (room, state) in tasks) {
+ if (state is null) continue;
+ if (!matchingStates.ContainsKey(state.Membership))
+ matchingStates.Add(state.Membership, new());
+ var roomInfo = new RoomInfo() {
+ Room = room
+ };
+ matchingStates[state.Membership].Add(roomInfo);
+ roomInfo.StateEvents.Add(new() {
+ Type = RoomNameEventContent.EventId,
+ TypedContent = new RoomNameEventContent() {
+ Name = await room.GetNameOrFallbackAsync(4)
+ }
+ });
+ StateHasChanged();
+ if (mxid != UserId) {
+ _semaphoreSlim.Release();
+ return; //abort if changed
+ }
+ }
+ StateHasChanged();
+ }
+ currentHs = null;
+ StateHasChanged();
+ _semaphoreSlim.Release();
+ }
+
+ public async Task<(GenericRoom roomId, RoomMemberEventContent? content)> GetMembershipAsync(GenericRoom room, string mxid) {
+ return (room, await room.GetStateOrNullAsync<RoomMemberEventContent>(RoomMemberEventContent.EventId, mxid));
+ }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
index 5823757..08b21dd 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Create.razor
@@ -88,7 +88,7 @@
<tr>
<td>Room icon:</td>
<td>
- <img src="@hsResolver.ResolveMediaUri(Homeserver.ServerName, roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/>
+ <img src="@Homeserver.ResolveMediaUri(roomAvatarEvent.Url)" style="width: 128px; height: 128px; border-radius: 50%;"/>
<div style="display: inline-block; vertical-align: middle;">
<FancyTextBox @bind-Value="@roomAvatarEvent.Url"></FancyTextBox><br/>
<InputFile OnChange="RoomIconFilePicked"></InputFile>
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
index fd32cb3..60f4f62 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -156,6 +156,7 @@
Status = $"Got {Rooms.Count} rooms so far! {queue.Count} entries in processing queue...";
}
RenderContents |= queue.Count == 0;
+ if (queue.Count > 10) RenderContents = false;
await Task.Delay(RenderContents ? 25 : 25);
}
else {
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor
index 1f4a923..01bf555 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Timeline.razor
@@ -47,10 +47,14 @@
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 {
- RoomMessageEventContent => typeof(TimelineMessageItem),
+ RoomCanonicalAliasEventContent => typeof(TimelineCanonicalAliasItem),
+ RoomHistoryVisibilityEventContent => typeof(TimelineHistoryVisibilityItem),
+ RoomTopicEventContent => typeof(TimelineRoomTopicItem),
RoomMemberEventContent => typeof(TimelineMemberItem),
+ RoomMessageEventContent => typeof(TimelineMessageItem),
RoomCreateEventContent => typeof(TimelineRoomCreateItem),
+ RoomNameEventContent => typeof(TimelineRoomNameItem),
_ => typeof(TimelineUnknownItem)
- };
+ };
}
diff --git a/MatrixRoomUtils.Web/Pages/ServerInfo.razor b/MatrixRoomUtils.Web/Pages/ServerInfo.razor
new file mode 100644
index 0000000..5b3f1c1
--- /dev/null
+++ b/MatrixRoomUtils.Web/Pages/ServerInfo.razor
@@ -0,0 +1,235 @@
+@page "/ServerInfo/{Homeserver}"
+@using LibMatrix.Homeservers
+@using LibMatrix.Responses
+@using ArcaneLibs.Extensions
+<h3>ServerInfo</h3>
+<hr/>
+@if (ServerVersionResponse is not null) {
+ <p>Server version: @ServerVersionResponse.Server.Name @ServerVersionResponse.Server.Version</p>
+ <pre>@ServerVersionResponse?.ToJson(ignoreNull: true)</pre>
+ <br/>
+}
+@if (ClientVersionsResponse is not null) {
+ <p>Client versions:</p>
+ <details>
+ <summary>JSON data</summary>
+ <pre>@ClientVersionsResponse?.ToJson(ignoreNull: true)</pre>
+ </details>
+ <u>Spec versions</u>
+ <table>
+ <thead>
+ <td></td>
+ <td>Version</td>
+ <td>Release date</td>
+ </thead>
+ @foreach (var (version, info) in ClientVersions) {
+ <tr>
+ <td>@(ClientVersionsResponse.Versions.Contains(version) ? "\u2714" : "\u274c")</td>
+ <td><a href="@info.SpecUrl">@info.Name</a></td>
+ <td>@info.Released</td>
+ </tr>
+ }
+
+ @foreach (var version in ClientVersionsResponse.Versions) {
+ if (!ClientVersions.ContainsKey(version)) {
+ <tr>
+ <td>@("\u2714")</td>
+ <td><a href="https://spec.matrix.org/@version">Unknown version: @version</a></td>
+ <td></td>
+ </tr>
+ }
+ }
+ </table>
+ <u>Unstable features</u>
+ <table>
+ <thead>
+ <td style="padding-right: 8px;">Supported</td>
+ <td style="padding-right: 8px;">Enabled</td>
+ <td style="padding-right: 8px;">Name</td>
+ </thead>
+ @* @foreach (var (version, info) in ClientVersions) { *@
+ @* <tr> *@
+ @* *@
+ @* <td>@("\u2714")</td> *@
+ @* <td>@(ClientVersionsResponse.Versions.Contains(version) ? "\u2714" : "\u274c")</td> *@
+ @* <td>@info.Released</td> *@
+ @* </tr> *@
+ @* } *@
+
+ @foreach (var version in ClientVersionsResponse.UnstableFeatures) {
+ if (!ClientVersions.ContainsKey(version.Key)) {
+ <tr>
+ <td>@("\u2714")</td>
+ <td>@(version.Value ? "\u2714" : "\u274c")</td>
+ <td>@version.Key</td>
+ </tr>
+ }
+ }
+ </table>
+}
+
+
+@code {
+
+ [Parameter]
+ public string? Homeserver { get; set; }
+
+ public ServerVersionResponse? ServerVersionResponse { get; set; }
+ public ClientVersionsResponse? ClientVersionsResponse { get; set; }
+
+ protected override async Task OnParametersSetAsync() {
+ if (Homeserver is not null) {
+ var rhs = await hsProvider.GetRemoteHomeserver(Homeserver);
+ ServerVersionResponse = await rhs.GetServerVersionAsync();
+ ClientVersionsResponse = await rhs.GetClientVersionsAsync();
+ }
+ base.OnParametersSetAsync();
+ }
+
+ private class ClientVersionInfo {
+ public string Name { get; set; }
+ public string SpecUrl { get; set; }
+ public DateTime Released { get; set; }
+ }
+
+ private Dictionary<string, ClientVersionInfo> ClientVersions = new() {
+ {
+ "legacy",
+ new() {
+ Name = "Legacy: Last draft before formal release of r0.0.0",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/legacy/"
+ }
+ },
+ {
+ "r0.0.0",
+ new() {
+ Name = "r0.0.0: Initial release: media repo, sync v2",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.0.0/"
+ }
+ },
+ {
+ "r0.0.1",
+ new() {
+ Name = "r0.0.1: User-interactive authentication, groups, read receipts, presence",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.0.1/"
+ }
+ },
+ {
+ "r0.1.0",
+ new() {
+ Name = "r0.1.0: Device management, account data, push rules, VoIP",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.1.0/"
+ }
+ },
+ {
+ "r0.2.0",
+ new() {
+ Name = "r0.2.0: Clarifications",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/client_server/r0.2.0.html"
+ }
+ },
+ {
+ "r0.3.0",
+ new() {
+ Name = "r0.3.0: Device management",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/client_server/r0.3.0.html"
+ }
+ },
+ {
+ "r0.4.0",
+ new() {
+ Name = "r0.4.0: Room directory",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.4.0/"
+ }
+ },
+ {
+ "r0.5.0",
+ new() {
+ Name = "r0.5.0: Push rules, VoIP, groups, read receipts, presence",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.5.0/"
+ }
+ },
+ {
+ "r0.6.0",
+ new() {
+ Name = "r0.6.0: Unbinding 3PIDs, clean up bindings from register",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.6.0/"
+ }
+ },
+ {
+ "r0.6.1",
+ new(){
+ Name = "r0.6.1: Moderation policies, better alias handling",
+ Released = DateTime.Parse("2014-07-01 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/legacy/r0.6.1/"
+ }
+ },
+ {
+ "v1.1",
+ new() {
+ Name = "v1.1: Key backup, knocking",
+ Released = DateTime.Parse("2021-11-09 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/v1.1/"
+ }
+ }, {
+ "v1.2",
+ new() {
+ Name = "v1.2: ",
+ Released = DateTime.Parse("2022-02-02 00:00:00 +0000"),
+ SpecUrl = "https://spec.matrix.org/v1.2/"
+ }
+ }, {
+ "v1.3",
+ new() {
+ Name = "v1.3: ",
+ Released = DateTime.Parse("2022-06-15 00:00:00 +0100"),
+ SpecUrl = "https://spec.matrix.org/v1.3/"
+ }
+ }, {
+ "v1.4",
+ new() {
+ Name = "v1.4: ",
+ Released = DateTime.Parse("2022-09-29 00:00:00 +0100"),
+ SpecUrl = "https://spec.matrix.org/v1.4/"
+ }
+ }, {
+ "v1.5",
+ new() {
+ Name = "v1.5: ",
+ Released = DateTime.Parse("2022-11-17 08:22:11 -0700"),
+ SpecUrl = "https://spec.matrix.org/v1.5/"
+ }
+ }, {
+ "v1.6",
+ new () {
+ Name = "v1.6: ",
+ Released = DateTime.Parse("2023-02-14 08:25:40 -0700"),
+ SpecUrl = "https://spec.matrix.org/v1.6"
+ }
+ }, {
+ "v1.7",
+ new () {
+ Name = "v1.7: ",
+ Released = DateTime.Parse("2023-05-25 09:47:21 -0600"),
+ SpecUrl = "https://spec.matrix.org/v1.7"
+ }
+ }, {
+ "v1.8",
+ new () {
+ Name = "v1.8: Room version 11",
+ Released = DateTime.Parse("2023-08-23 09:23:53 -0600"),
+ SpecUrl = "https://spec.matrix.org/v1.8"
+ }
+ }
+ };
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor b/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor
index 0ab0bd2..dbf2f5f 100644
--- a/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor
+++ b/MatrixRoomUtils.Web/Pages/Tools/KnownHomeserverList.razor
@@ -7,51 +7,43 @@
<hr/>
@if (!IsFinished) {
- <p>Loading... Please wait...</p>
- <progress value="@QueryProgress.ProcessedRooms" max="@QueryProgress.TotalRooms"></progress>
- <p>@QueryProgress.ProcessedRooms / @QueryProgress.TotalRooms</p>
- @foreach (var (room, state) in QueryProgress.ProcessedUsers.Where(x => !x.Value.IsFinished).OrderByDescending(x => x.Value.Total).ToList()) {
- @if (state.Blocked) {
- <p>🔒 @room.RoomId - @state.Processed / @state.Total, @state.Timing.Elapsed elapsed...</p>
- }
- else if (state.Slowmode) {
- <p>🐢 @room.RoomId - @state.Processed / @state.Total, @state.Timing.Elapsed elapsed...</p>
- }
- else {
- <p>@room.RoomId - @state.Processed / @state.Total, @state.Timing.Elapsed elapsed...</p>
- }
- <progress value="@state.Processed" max="@state.Total"></progress>
- }
+ <p>
+ <b>Loading...</b>
+ </p>
}
-else {
- @foreach (var server in Homeservers.OrderByDescending(x => x.KnownUserCount).ThenBy(x => x.Server).ToList()) {
- <p>@server.Server - @server.KnownUserCount</p>
- }
+
+@foreach (var (homeserver, members) in counts.OrderByDescending(x => x.Value)) {
+ <p>@homeserver - @members</p>
}
<hr/>
@code {
- List<HomeserverInfo> Homeservers = new();
+ Dictionary<string, List<string>> homeservers { get; set; } = new();
+ Dictionary<string, int> counts { get; set; } = new();
+ // List<HomeserverInfo> Homeservers = new();
bool IsFinished { get; set; }
- HomeserverInfoQueryProgress QueryProgress { get; set; } = new();
- AuthenticatedHomeserverGeneric hs { get; set; }
+ // HomeserverInfoQueryProgress QueryProgress { get; set; } = new();
+ AuthenticatedHomeserverGeneric? hs { get; set; }
+
protected override async Task OnInitializedAsync() {
hs = await MRUStorage.GetCurrentSessionOrNavigate();
if (hs is null) return;
- var sw = Stopwatch.StartNew();
- Homeservers = await GetHomeservers(progressCallback: async progress => {
- if (sw.ElapsedMilliseconds > 1000) {
- Console.WriteLine("Progress updated...");
- QueryProgress = progress;
- StateHasChanged();
- Console.WriteLine("Progress rendered!");
- sw.Restart();
- await Task.Delay(100);
- return true;
+ var fetchTasks = (await hs.GetJoinedRooms()).Select(x=>x.GetMembersByHomeserverAsync()).ToAsyncEnumerable();
+ await foreach (var result in fetchTasks) {
+ foreach (var (resHomeserver, resMembers) in result) {
+ if (!homeservers.TryAdd(resHomeserver, resMembers)) {
+ homeservers[resHomeserver].AddRange(resMembers);
+ }
+ counts[resHomeserver] = homeservers[resHomeserver].Count;
}
- Console.WriteLine($"Progress updated, but not rendering because only {sw.ElapsedMilliseconds}ms elapsed since last call...");
- return false;
- });
+ // StateHasChanged();
+ // await Task.Delay(250);
+ }
+
+ foreach (var resHomeserver in homeservers.Keys) {
+ homeservers[resHomeserver] = homeservers[resHomeserver].Distinct().ToList();
+ counts[resHomeserver] = homeservers[resHomeserver].Count;
+ }
IsFinished = true;
StateHasChanged();
@@ -59,64 +51,4 @@ else {
await base.OnInitializedAsync();
}
- private async Task<List<HomeserverInfo>> GetHomeservers(int memberLimit = 1000, Func<HomeserverInfoQueryProgress, Task<bool>>? progressCallback = null) {
- HomeserverInfoQueryProgress progress = new();
- List<HomeserverInfo> homeServers = new();
-
- var rooms = await hs.GetJoinedRooms();
- progress.TotalRooms = rooms.Count;
-
- var semaphore = new SemaphoreSlim(4);
- var tasks = rooms.Select(async room => {
- await semaphore.WaitAsync();
- progress.ProcessedUsers.Add(room, new HomeserverInfoQueryProgress.State());
- Console.WriteLine($"Fetching states for room ({rooms.IndexOf(room)}/{rooms.Count}) ({room.RoomId})");
- var states = room.GetMembersAsync();
- await foreach (var state in states) {
- if (state.Type is not "m.room.member") continue;
- progress.ProcessedUsers[room].Total++;
-
- if (homeServers.Any(x => x.Server == state.StateKey.Split(':')[1])) continue;
- homeServers.Add(new HomeserverInfo { Server = state.StateKey.Split(':')[1] });
- Console.WriteLine($"Added new homeserver {state.StateKey.Split(':')[1]}");
- }
- semaphore.Release();
- progress.ProcessedUsers[room].IsFinished = true;
- progress.ProcessedRooms++;
- if (progressCallback is not null)
- await progressCallback.Invoke(progress);
- });
- // var results = tasks.ToAsyncEnumerable();
- await Task.WhenAll(tasks);
-
- Console.WriteLine("Calculating member counts...");
- homeServers.ForEach(x => x.KnownUserCount = x.KnownUsers.Count);
- Console.WriteLine(homeServers.First(x => x.Server == "rory.gay").ToJson());
- Console.WriteLine("Recalculated!");
- return homeServers;
- }
-
- class HomeserverInfo {
- public string Server { get; set; }
- public int? KnownUserCount { get; set; }
- public List<string> KnownUsers { get; } = new();
- }
-
- class HomeserverInfoQueryProgress {
- public int ProcessedRooms { get; set; }
- public int TotalRooms { get; set; }
- public Dictionary<GenericRoom, State> ProcessedUsers { get; } = new();
- public List<HomeserverInfo> CurrentState { get; set; } = new();
-
- public class State {
- public int Processed { get; set; }
- public int Total { get; set; }
- public bool Blocked { get; set; }
- public bool Slowmode { get; set; }
- public float Progress => (float)Processed / Total;
- public bool IsFinished { get; set; }
- public Stopwatch Timing { get; } = Stopwatch.StartNew();
- }
- }
-
-}
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor b/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor
index 59ec79e..20aa639 100644
--- a/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor
+++ b/MatrixRoomUtils.Web/Pages/Tools/MediaLocator.razor
@@ -94,7 +94,7 @@
lines.ToList().ForEach(async line => {
await sem.WaitAsync();
try {
- homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).client);
+ homeservers.Add((await hsResolver.ResolveHomeserverFromWellKnown(line)).Client);
StateHasChanged();
}
catch (Exception e) {
diff --git a/MatrixRoomUtils.Web/Pages/User/DMManager.razor b/MatrixRoomUtils.Web/Pages/User/DMManager.razor
index 92e1bc2..f753f18 100644
--- a/MatrixRoomUtils.Web/Pages/User/DMManager.razor
+++ b/MatrixRoomUtils.Web/Pages/User/DMManager.razor
@@ -40,7 +40,14 @@
var roomList = new List<RoomInfo>();
DMRooms.Add(await Homeserver.GetProfileAsync(userId), roomList);
foreach (var room in rooms) {
- roomList.Add(new RoomInfo() { Room = Homeserver.GetRoom(room) });
+ var roomInfo = new RoomInfo() { Room = Homeserver.GetRoom(room) };
+ roomList.Add(roomInfo);
+ roomInfo.StateEvents.Add(new() {
+ Type = RoomNameEventContent.EventId,
+ TypedContent = new RoomNameEventContent() {
+ Name = await Homeserver.GetRoom(room).GetNameOrFallbackAsync(4)
+ }
+ });
}
StateHasChanged();
}
@@ -51,6 +58,4 @@
await base.OnInitializedAsync();
}
-
-
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/MxcImage.razor b/MatrixRoomUtils.Web/Shared/MxcImage.razor
new file mode 100644
index 0000000..f54c1f8
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/MxcImage.razor
@@ -0,0 +1,43 @@
+<img class="@Class" src="@ResolvedUri" style="@Style"/>
+@code {
+ private string _mxcUri;
+ private string _style;
+ private string _resolvedUri;
+
+ [Parameter]
+ public string MxcUri {
+ get => _mxcUri ?? "";
+ set {
+ _mxcUri = value;
+ UriHasChanged(value);
+ }
+ }
+
+ [Parameter]
+ public string Style {
+ get => _style;
+ set {
+ _style = value;
+ StateHasChanged();
+ }
+ }
+ [Parameter]
+ public RemoteHomeserver? Homeserver { get; set; }
+
+ private string ResolvedUri {
+ get => _resolvedUri;
+ set {
+ _resolvedUri = value;
+ StateHasChanged();
+ }
+ }
+
+ private async Task UriHasChanged(string value) {
+ var uri = value[5..].Split('/');
+ ResolvedUri = (Homeserver ?? await hsProvider.GetRemoteHomeserver(uri[0])).ResolveMediaUri(value);
+ }
+
+ [Parameter]
+ public string Class { get; set; }
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
index a24ccad..970526d 100644
--- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor
+++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
@@ -9,14 +9,14 @@
@if (RoomInfo is not null) {
<div class="roomListItem @(HasDangerousRoomVersion ? "dangerousRoomVersion" : HasOldRoomVersion ? "oldRoomVersion" : "")" id="@RoomInfo.Room.RoomId">
@if (OwnMemberState != null) {
- <img class="avatar32 @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "highlightChange" : "") @(ChildContent is not null ? "vcenter" : "")"
- src="@(hs.ResolveMediaUri(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl) ?? "/icon-192.png")"/>
+ <MxcImage Class="@("avatar32" + (OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? " highlightChange" : "") + (ChildContent is not null ? " vcenter" : ""))"
+ MxcUri="@(OwnMemberState.AvatarUrl ?? GlobalProfile.AvatarUrl)"/>
<span class="centerVertical border75 @(OwnMemberState?.AvatarUrl != GlobalProfile?.AvatarUrl ? "highlightChange" : "")">
@(OwnMemberState?.DisplayName ?? GlobalProfile?.DisplayName ?? "Loading...")
</span>
<span class="centerVertical noLeftPadding">-></span>
}
- <img class="avatar32" src="@hs?.ResolveMediaUri(RoomInfo.RoomIcon)" style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/>
+ <MxcImage Class="avatar32" MxcUri="RoomInfo.RoomIcon" Style="@(ChildContent is not null ? "vertical-align: middle;" : "")"/>
<div class="inlineBlock">
<span class="centerVertical">@RoomInfo.RoomName</span>
@if (ChildContent is not null) {
@@ -36,7 +36,13 @@ else {
public RenderFragment? ChildContent { get; set; }
[Parameter]
- public RoomInfo? RoomInfo { get; set; }
+ public RoomInfo? RoomInfo {
+ get => _roomInfo;
+ set {
+ _roomInfo = value;
+ OnParametersSetAsync();
+ }
+ }
[Parameter]
public bool ShowOwnProfile { get; set; } = false;
@@ -48,42 +54,52 @@ else {
public UserProfileResponse? GlobalProfile { get; set; }
[Parameter]
- public bool LoadData { get; set; } = false;
+ public bool LoadData {
+ get => _loadData;
+ set {
+ _loadData = value;
+ OnParametersSetAsync();
+ }
+ }
private bool HasOldRoomVersion { get; set; } = false;
private bool HasDangerousRoomVersion { get; set; } = false;
private static SemaphoreSlim _semaphoreSlim = new(8);
+ private RoomInfo? _roomInfo;
+ private bool _loadData = false;
private static AuthenticatedHomeserverGeneric? hs { get; set; }
protected override async Task OnParametersSetAsync() {
- RoomInfo.PropertyChanged += (_, a) => {
- Console.WriteLine(a.PropertyName);
- StateHasChanged();
- };
-
- if (LoadData) {
- try {
- await RoomInfo.GetStateEvent("m.room.create");
- if (ShowOwnProfile)
- OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.WhoAmI.UserId)).TypedContent as RoomMemberEventContent;
-
- await RoomInfo.GetStateEvent("m.room.name");
- await RoomInfo.GetStateEvent("m.room.avatar");
- }
- catch (MatrixException e) {
- if (e.ErrorCode == "M_FORBIDDEN") {
- LoadData = false;
- RoomInfo.StateEvents.Add(new() {
- Type = "m.room.create",
- TypedContent = new RoomCreateEventContent() { RoomVersion = "0" }
- });
- RoomInfo.StateEvents.Add(new() {
- Type = "m.room.name",
- TypedContent = new RoomNameEventContent() {
- Name = "M_FORBIDDEN: Are you a member of this room? " + RoomInfo.Room.RoomId
- }
- });
+ if (RoomInfo != null) {
+ RoomInfo.PropertyChanged += (_, a) => {
+ Console.WriteLine(a.PropertyName);
+ StateHasChanged();
+ };
+
+ if (LoadData) {
+ try {
+ await RoomInfo.GetStateEvent("m.room.create");
+ if (ShowOwnProfile)
+ OwnMemberState ??= (await RoomInfo.GetStateEvent("m.room.member", hs.WhoAmI.UserId)).TypedContent as RoomMemberEventContent;
+
+ await RoomInfo.GetStateEvent("m.room.name");
+ await RoomInfo.GetStateEvent("m.room.avatar");
+ }
+ catch (MatrixException e) {
+ if (e.ErrorCode == "M_FORBIDDEN") {
+ LoadData = false;
+ RoomInfo.StateEvents.Add(new() {
+ Type = "m.room.create",
+ TypedContent = new RoomCreateEventContent() { RoomVersion = "0" }
+ });
+ RoomInfo.StateEvents.Add(new() {
+ Type = "m.room.name",
+ TypedContent = new RoomNameEventContent() {
+ Name = "M_FORBIDDEN: Are you a member of this room? " + RoomInfo.Room.RoomId
+ }
+ });
+ }
}
}
}
@@ -170,5 +186,4 @@ else {
// }
// }
-}
-
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor
index 9efeaab..8d608e3 100644
--- a/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/BaseTimelineItem.razor
@@ -1,5 +1,7 @@
@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
@using LibMatrix.Homeservers
+@using LibMatrix.Responses
<h3>BaseTimelineItem</h3>
@code {
@@ -13,4 +15,19 @@
[Parameter]
public AuthenticatedHomeserverGeneric Homeserver { get; set; }
-}
+ public List<StateEventResponse> EventsBefore => Events.TakeWhile(e => e.EventId != Event.EventId).ToList();
+
+ public List<StateEventResponse> MatchingEventsBefore => EventsBefore.Where(x => x.Type == Event.Type && x.StateKey == Event.StateKey).ToList();
+
+ public StateEventResponse? PreviousState => MatchingEventsBefore.LastOrDefault();
+
+ public RoomMemberEventContent? CurrentSenderMemberEventContent => EventsBefore.LastOrDefault(x => x.Type == "m.room.member" && x.StateKey == Event.Sender)?
+ .TypedContent as RoomMemberEventContent;
+
+ public UserProfileResponse CurrentSenderProfile => new() { DisplayName = CurrentSenderMemberEventContent?.DisplayName, AvatarUrl = CurrentSenderMemberEventContent?.AvatarUrl };
+
+ public bool HasPreviousMessage => EventsBefore.Last() is { Type: "m.room.message" } response && response.Sender == Event.Sender;
+
+
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor
new file mode 100644
index 0000000..1213432
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineCanonicalAliasItem.razor
@@ -0,0 +1,27 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.Responses
+@inherits BaseTimelineItem
+
+@if (currentEventContent is not null) {
+ @if (previousEventContent is null) {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the room alias to "@currentEventContent.Alias"</i>
+ }
+ else {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the room name from "@previousEventContent.Alias" to "@currentEventContent.Alias"</i>
+ }
+}
+else {
+ <details>
+ <summary>Unknown event @Event.Type (@Event.StateKey)</summary>
+ <pre>
+ @Event.ToJson()
+ </pre>
+ </details>
+}
+
+@code {
+ private RoomCanonicalAliasEventContent? previousEventContent => PreviousState?.TypedContent as RoomCanonicalAliasEventContent;
+
+ private RoomCanonicalAliasEventContent? currentEventContent => Event.TypedContent as RoomCanonicalAliasEventContent;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor
new file mode 100644
index 0000000..172a38c
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineHistoryVisibilityItem.razor
@@ -0,0 +1,27 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.Responses
+@inherits BaseTimelineItem
+
+@if (currentEventContent is not null) {
+ @if (previousEventContent is null) {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the history visibility to "@currentEventContent.HistoryVisibility"</i>
+ }
+ else {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the history visibility from "@previousEventContent.HistoryVisibility" to "@currentEventContent.HistoryVisibility"</i>
+ }
+}
+else {
+ <details>
+ <summary>Unknown event @Event.Type (@Event.StateKey)</summary>
+ <pre>
+ @Event.ToJson()
+ </pre>
+ </details>
+}
+
+@code {
+ private RoomHistoryVisibilityEventContent? previousEventContent => PreviousState?.TypedContent as RoomHistoryVisibilityEventContent;
+
+ private RoomHistoryVisibilityEventContent? currentEventContent => Event.TypedContent as RoomHistoryVisibilityEventContent;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor
index ed4dceb..3b18b95 100644
--- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMemberItem.razor
@@ -15,7 +15,12 @@
<i>@Event.StateKey changed their display name to @(roomMemberData.DisplayName ?? Event.Sender)</i>
break;
case "join":
- <i><InlineUserItem User="@(new UserProfileResponse())" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> joined</i>
+ @if (prevRoomMemberData is null) {
+ <i><InlineUserItem User="@(new UserProfileResponse() { DisplayName = roomMemberData.DisplayName, AvatarUrl = roomMemberData.AvatarUrl })" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> joined</i>
+ }
+ else {
+ <i><InlineUserItem User="@(new UserProfileResponse() { DisplayName = prevRoomMemberData.DisplayName, AvatarUrl = prevRoomMemberData.AvatarUrl })" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed their profile to <InlineUserItem User="@(new UserProfileResponse() { DisplayName = roomMemberData.DisplayName, AvatarUrl = roomMemberData.AvatarUrl })" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem></i>
+ }
break;
case "leave":
<i>@Event.StateKey left</i>
@@ -43,5 +48,6 @@ else {
@code {
private RoomMemberEventContent? roomMemberData => Event.TypedContent as RoomMemberEventContent;
+ private RoomMemberEventContent? prevRoomMemberData => PreviousState?.TypedContent as RoomMemberEventContent;
}
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor
index 8073406..81956b0 100644
--- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineMessageItem.razor
@@ -1,10 +1,34 @@
@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec
@inherits BaseTimelineItem
-<pre>
- @Event.RawContent?.ToJson(indent: false)
-</pre>
+<span>
+ @if (!HasPreviousMessage) {
+ <span><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem>:</span><br/>
+ }
+ @switch (currentEventContent.MessageType) {
+ case "m.text": {
+ @foreach (var line in currentEventContent.Body.Split('\n')) {
+ <span>@line</span><br/>
+ }
+ break;
+ }
+ case "m.image": {
+ <i>@currentEventContent.Body</i><br/>
+ <img src="@Homeserver.ResolveMediaUri(currentEventContent.Url)">
+ break;
+ }
+ default: {
+ <pre>
+ @Event.RawContent?.ToJson(indent: false)
+ </pre>
+ break;
+ }
+ }
+</span>
@code {
+ private RoomMessageEventContent? previousEventContent => PreviousState?.TypedContent as RoomMessageEventContent;
-}
+ private RoomMessageEventContent? currentEventContent => Event.TypedContent as RoomMessageEventContent;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor
index 2d05151..f3e6c7e 100644
--- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomCreateItem.razor
@@ -2,11 +2,11 @@
@using LibMatrix.EventTypes.Spec.State
@inherits BaseTimelineItem
-<p>
+<i>
@Event.Sender created the room with room version @CreationEventContent.RoomVersion
- @(CreationEventContent.Federate ?? false ? "and" : "without") federating with other servers.<br/>
+ @(CreationEventContent.Federate ?? true ? "and" : "without") federating with other servers.<br/>
This room is of type @(CreationEventContent.Type ?? "Untyped room (usually a chat room)")
-</p>
+</i>
<pre>
@Event.RawContent?.ToJson(indent: false)
</pre>
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor
new file mode 100644
index 0000000..eeec3de
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomNameItem.razor
@@ -0,0 +1,27 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.Responses
+@inherits BaseTimelineItem
+
+@if (currentEventContent is not null) {
+ @if (previousEventContent is null) {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the room name to "@currentEventContent.Name"</i>
+ }
+ else {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the room name from "@previousEventContent.Name" to "@currentEventContent.Name"</i>
+ }
+}
+else {
+ <details>
+ <summary>Unknown event @Event.Type (@Event.StateKey)</summary>
+ <pre>
+ @Event.ToJson()
+ </pre>
+ </details>
+}
+
+@code {
+ private RoomNameEventContent? previousEventContent => PreviousState?.TypedContent as RoomNameEventContent;
+
+ private RoomNameEventContent? currentEventContent => Event.TypedContent as RoomNameEventContent;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor
new file mode 100644
index 0000000..7ef17a8
--- /dev/null
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineRoomTopicItem.razor
@@ -0,0 +1,37 @@
+@using ArcaneLibs.Extensions
+@using LibMatrix.EventTypes.Spec.State
+@using LibMatrix.Responses
+@inherits BaseTimelineItem
+
+@if (currentEventContent is not null) {
+ @if (previousEventContent is null) {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> set the room topic to</i><br/>
+ <pre>
+ @currentEventContent.Topic
+ </pre>
+ }
+ else {
+ <i><InlineUserItem User="@CurrentSenderProfile" Homeserver="@Homeserver" UserId="@Event.StateKey"></InlineUserItem> changed the room topic from</i><br/>
+ <pre>
+ @previousEventContent.Topic
+ </pre><br/>
+ <i>to</i><br/>
+ <pre>
+ @currentEventContent.Topic
+ </pre>
+ }
+}
+else {
+ <details>
+ <summary>Unknown event @Event.Type (@Event.StateKey)</summary>
+ <pre>
+ @Event.ToJson()
+ </pre>
+ </details>
+}
+
+@code {
+ private RoomTopicEventContent? previousEventContent => PreviousState?.TypedContent as RoomTopicEventContent;
+
+ private RoomTopicEventContent? currentEventContent => Event.TypedContent as RoomTopicEventContent;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor
index 1ab530d..4f05b30 100644
--- a/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor
+++ b/MatrixRoomUtils.Web/Shared/TimelineComponents/TimelineUnknownItem.razor
@@ -6,7 +6,7 @@
<summary>
<i style="color: red;">Unknown event type: <pre style="display: inline;">@Event.Type</pre></i>
</summary>
- <pre>@Event.ToJson()</pre>
+ <pre>@Event.ToJson(ignoreNull: true)</pre>
</details>
</div>
diff --git a/MatrixRoomUtils.Web/_Imports.razor b/MatrixRoomUtils.Web/_Imports.razor
index 91b69fb..0d07b3a 100644
--- a/MatrixRoomUtils.Web/_Imports.razor
+++ b/MatrixRoomUtils.Web/_Imports.razor
@@ -11,8 +11,8 @@
@using MatrixRoomUtils.Web
@using MatrixRoomUtils.Web.Classes
@using MatrixRoomUtils.Web.Shared
-
@using ArcaneLibs.Blazor.Components
+@using LibMatrix.Homeservers
@inject NavigationManager NavigationManager
@inject MRUStorageWrapper MRUStorage
diff --git a/MatrixRoomUtils.sln.DotSettings.user b/MatrixRoomUtils.sln.DotSettings.user
index b15cbb5..7597038 100644
--- a/MatrixRoomUtils.sln.DotSettings.user
+++ b/MatrixRoomUtils.sln.DotSettings.user
@@ -44,6 +44,12 @@
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=c33adfe1_002D4af3_002D4c1e_002D9689_002De5e34a9f9113/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;LibMatrix&gt; #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
<Project Location="/home/root@Rory/git/Matrix/MatrixRoomUtils" Presentation="&lt;LibMatrix&gt;" />
</SessionState></s:String>
+ <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=2cb12623_002D4918_002D4176_002D9b4a_002D88d846ccd3ed_0023LibMatrix_002EExampleBot/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=48dbb05f_002Db007_002D4b24_002D89b3_002D3cc177c79007_0023MediaModeratorPoC/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=95052ee6_002D7513_002D46fb_002D91bd_002Dee82026b42f1_0023PluralContactBotPoC/@EntryIndexedValue">True</s:Boolean>
+
+
+
diff --git a/MxApiExtensions b/MxApiExtensions
-Subproject cb719c315e225ba97e32b2dca7d9b184755234c
+Subproject 2e8aa30daa4a33fa33622bccb344dfc24483e32
|