diff --git a/MatrixRoomUtils.Core/Extensions/ClassCollector.cs b/MatrixRoomUtils.Core/Extensions/ClassCollector.cs
index 9d3d3c0..d4ba838 100644
--- a/MatrixRoomUtils.Core/Extensions/ClassCollector.cs
+++ b/MatrixRoomUtils.Core/Extensions/ClassCollector.cs
@@ -19,12 +19,4 @@ public class ClassCollector<T> where T : class {
public List<Type> ResolveFromAssembly(Assembly assembly) => assembly.GetTypes()
.Where(x => x is { IsClass: true, IsAbstract: false } && x.GetInterfaces().Contains(typeof(T))).ToList();
- // {
- // List<Type> ret = new();
- // foreach (var x in assembly.GetTypes().Where(x => x is { IsClass: true, IsAbstract: false } && x.GetInterfaces().Contains(typeof(T))).ToList()) {
- // // Console.WriteLine($"[!!] Found class {x.FullName}");
- // ret.Add(x);
- // }
- // return ret;
- // }
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
index 695e8e3..9ac9c6b 100644
--- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
@@ -19,40 +19,42 @@ public static class HttpClientExtensions {
}
public class MatrixHttpClient : HttpClient {
- public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
- Console.WriteLine($"Sending request to {request.RequestUri}");
- try
- {
- HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
+ public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
+ CancellationToken cancellationToken) {
+ Console.WriteLine($"Sending request to {request.RequestUri}");
+ try {
+ HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey =
+ new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
request.Options.Set(WebAssemblyEnableStreamingResponseKey, true);
- // var asm = Assembly.Load("Microsoft.AspNetCore.Components.WebAssembly");
- // var browserHttpHandlerType = asm.GetType("Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequestMessageExtensions", true);
- // var browserHttpHandlerMethod = browserHttpHandlerType.GetMethod("SetBrowserResponseStreamingEnabled", BindingFlags.Public | BindingFlags.Static);
- // browserHttpHandlerMethod?.Invoke(null, new object[] {request, true});
}
- catch (Exception e)
- {
+ catch (Exception e) {
Console.WriteLine("Failed to set browser response streaming:");
Console.WriteLine(e);
}
+
var a = await base.SendAsync(request, cancellationToken);
if (!a.IsSuccessStatusCode) {
var content = await a.Content.ReadAsStringAsync(cancellationToken);
if (content.StartsWith('{')) {
var ex = JsonSerializer.Deserialize<MatrixException>(content);
ex.RawContent = content;
- Console.WriteLine($"Failed to send request: {ex}");
+ Console.WriteLine($"Failed to send request: {ex}");
if (ex?.RetryAfterMs is not null) {
await Task.Delay(ex.RetryAfterMs.Value, cancellationToken);
- typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(request, 0);
+ typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)
+ ?.SetValue(request, 0);
return await SendAsync(request, cancellationToken);
}
+
throw ex!;
}
+
throw new InvalidDataException("Encountered invalid data:\n" + content);
}
+
return a;
}
+
// GetFromJsonAsync
public async Task<T> GetFromJsonAsync<T>(string requestUri, CancellationToken cancellationToken = default) {
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
diff --git a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
index 36da644..7701c9e 100644
--- a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
@@ -3,37 +3,149 @@ using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core.Extensions;
public static class JsonElementExtensions {
- public static bool FindExtraJsonElementFields([DisallowNull] this JsonElement? res, Type t) {
- var props = t.GetProperties();
- var unknownPropertyFound = false;
- foreach (var field in res.Value.EnumerateObject()) {
- if (props.Any(x => x.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name == field.Name)) continue;
- Console.WriteLine($"[!!] Unknown property {field.Name} in {t.Name}!");
+ public static bool FindExtraJsonElementFields(this JsonElement obj, Type objectType, string objectPropertyName) {
+ if (objectPropertyName == "content" && objectType == typeof(JsonObject))
+ objectType = typeof(StateEventResponse);
+ // if (t == typeof(JsonNode))
+ // return false;
+
+ Console.WriteLine($"{objectType.Name} {objectPropertyName}");
+ bool unknownPropertyFound = false;
+ var mappedPropsDict = objectType.GetProperties()
+ .Where(x => x.GetCustomAttribute<JsonPropertyNameAttribute>() is not null)
+ .ToDictionary(x => x.GetCustomAttribute<JsonPropertyNameAttribute>()!.Name, x => x);
+ objectType.GetProperties().Where(x => !mappedPropsDict.ContainsKey(x.Name))
+ .ToList().ForEach(x => mappedPropsDict.TryAdd(x.Name, x));
+
+ foreach (var field in obj.EnumerateObject()) {
+ if (mappedPropsDict.TryGetValue(field.Name, out var mappedProperty)) {
+ //dictionary
+ if (mappedProperty.PropertyType.IsGenericType &&
+ mappedProperty.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) {
+ unknownPropertyFound |= _checkDictionary(field, objectType, mappedProperty.PropertyType);
+ continue;
+ }
+
+ if (mappedProperty.PropertyType.IsGenericType &&
+ mappedProperty.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) {
+ unknownPropertyFound |= _checkList(field, objectType, mappedProperty.PropertyType);
+ continue;
+ }
+
+ if (field.Name == "content" && (objectType == typeof(StateEventResponse) || objectType == typeof(StateEvent))) {
+ unknownPropertyFound |= field.FindExtraJsonPropertyFieldsByValueKind(
+ StateEvent.GetStateEventType(obj.GetProperty("type").GetString()),
+ mappedProperty.PropertyType);
+ continue;
+ }
+
+ unknownPropertyFound |=
+ field.FindExtraJsonPropertyFieldsByValueKind(objectType, mappedProperty.PropertyType);
+ continue;
+ }
+
+ Console.WriteLine($"[!!] Unknown property {field.Name} in {objectType.Name}!");
unknownPropertyFound = true;
}
- if (unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
-
return unknownPropertyFound;
}
- public static bool FindExtraJsonObjectFields([DisallowNull] this JsonObject? res, Type t) {
- var props = t.GetProperties();
- var unknownPropertyFound = false;
- foreach (var field in res) {
- if (props.Any(x => x.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name == field.Key)) continue;
- Console.WriteLine($"[!!] Unknown property {field.Key} in {t.Name}!");
- unknownPropertyFound = true;
- // foreach (var propertyInfo in props) {
- // Console.WriteLine($"[!!] Known property {propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name} in {t.Name}!");
- // }
+
+ private static bool FindExtraJsonPropertyFieldsByValueKind(this JsonProperty field, Type containerType,
+ Type propertyType) {
+ if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
+ propertyType = propertyType.GetGenericArguments()[0];
}
- if (unknownPropertyFound) Console.WriteLine(res.ToJson());
+ bool switchResult = false;
+ switch (field.Value.ValueKind) {
+ case JsonValueKind.Array:
+ switchResult = field.Value.EnumerateArray().Aggregate(switchResult,
+ (current, element) => current | element.FindExtraJsonElementFields(propertyType, field.Name));
+ break;
+ case JsonValueKind.Object:
+ switchResult |= field.Value.FindExtraJsonElementFields(propertyType, field.Name);
+ break;
+ case JsonValueKind.True:
+ case JsonValueKind.False:
+ return _checkBool(field, containerType, propertyType);
+ case JsonValueKind.String:
+ return _checkString(field, containerType, propertyType);
+ case JsonValueKind.Number:
+ return _checkNumber(field, containerType, propertyType);
+ case JsonValueKind.Undefined:
+ case JsonValueKind.Null:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
- return unknownPropertyFound;
+ return switchResult;
+ }
+
+ private static bool _checkBool(this JsonProperty field, Type containerType, Type propertyType) {
+ if (propertyType == typeof(bool)) return true;
+ Console.WriteLine(
+ $"[!!] Encountered bool for {field.Name} in {containerType.Name}, the class defines {propertyType.Name}!");
+ return false;
+ }
+
+ private static bool _checkString(this JsonProperty field, Type containerType, Type propertyType) {
+ if (propertyType == typeof(string)) return true;
+ // ReSharper disable once BuiltInTypeReferenceStyle
+ if (propertyType == typeof(String)) return true;
+ Console.WriteLine(
+ $"[!!] Encountered string for {field.Name} in {containerType.Name}, the class defines {propertyType.Name}!");
+ return false;
+ }
+
+ private static bool _checkNumber(this JsonProperty field, Type containerType, Type propertyType) {
+ if (propertyType == typeof(int) ||
+ propertyType == typeof(double) ||
+ propertyType == typeof(float) ||
+ propertyType == typeof(decimal) ||
+ propertyType == typeof(long) ||
+ propertyType == typeof(short) ||
+ propertyType == typeof(uint) ||
+ propertyType == typeof(ulong) ||
+ propertyType == typeof(ushort) ||
+ propertyType == typeof(byte) ||
+ propertyType == typeof(sbyte))
+ return true;
+ Console.WriteLine(
+ $"[!!] Encountered number for {field.Name} in {containerType.Name}, the class defines {propertyType.Name}!");
+ return false;
+ }
+
+ private static bool _checkDictionary(this JsonProperty field, Type containerType, Type propertyType) {
+ var keyType = propertyType.GetGenericArguments()[0];
+ var valueType = propertyType.GetGenericArguments()[1];
+ valueType = Nullable.GetUnderlyingType(valueType) ?? valueType;
+ Console.WriteLine(
+ $"Encountered dictionary {field.Name} with key type {keyType.Name} and value type {valueType.Name}!");
+
+ return field.Value.EnumerateObject()
+ .Where(key => !valueType.IsPrimitive && valueType != typeof(string))
+ .Aggregate(false, (current, key) =>
+ current | key.FindExtraJsonPropertyFieldsByValueKind(containerType, valueType)
+ );
+ }
+
+ private static bool _checkList(this JsonProperty field, Type containerType, Type propertyType) {
+ var valueType = propertyType.GetGenericArguments()[0];
+ valueType = Nullable.GetUnderlyingType(valueType) ?? valueType;
+ Console.WriteLine(
+ $"Encountered list {field.Name} with value type {valueType.Name}!");
+
+ return field.Value.EnumerateArray()
+ .Where(key => !valueType.IsPrimitive && valueType != typeof(string))
+ .Aggregate(false, (current, key) =>
+ current | key.FindExtraJsonElementFields(valueType, field.Name)
+ );
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/Filters/SyncFilter.cs b/MatrixRoomUtils.Core/Filters/SyncFilter.cs
index 7957898..bc81101 100644
--- a/MatrixRoomUtils.Core/Filters/SyncFilter.cs
+++ b/MatrixRoomUtils.Core/Filters/SyncFilter.cs
@@ -4,47 +4,50 @@ namespace MatrixRoomUtils.Core.Filters;
public class SyncFilter {
[JsonPropertyName("account_data")]
- public AccountDataFilter? AccountData { get; set; }
+ public EventFilter? AccountData { get; set; }
[JsonPropertyName("presence")]
- public PresenceFilter? Presence { get; set; }
+ public EventFilter? Presence { get; set; }
[JsonPropertyName("room")]
public RoomFilter? Room { get; set; }
-}
-public class PresenceFilter {
- [JsonPropertyName("not_types")]
- public List<string>? NotTypes { get; set; }
-}
+ public class RoomFilter {
+ [JsonPropertyName("account_data")]
+ public StateFilter? AccountData { get; set; }
-public class RoomFilter {
- [JsonPropertyName("account_data")]
- public AccountDataFilter? AccountData { get; set; }
+ [JsonPropertyName("ephemeral")]
+ public StateFilter? Ephemeral { get; set; }
- [JsonPropertyName("ephemeral")]
- public EphemeralFilter? Ephemeral { get; set; }
+ [JsonPropertyName("state")]
+ public StateFilter? State { get; set; }
- public class EphemeralFilter {
- [JsonPropertyName("not_types")]
- public List<string>? NotTypes { get; set; }
- }
+ [JsonPropertyName("timeline")]
+ public StateFilter? Timeline { get; set; }
- [JsonPropertyName("state")]
- public StateFilter? State { get; set; }
- public class StateFilter {
- [JsonPropertyName("lazy_load_members")]
- public bool? LazyLoadMembers { get; set; }
+ public class StateFilter : EventFilter {
+ [JsonPropertyName("contains_url")]
+ public bool? ContainsUrl { get; set; }
- [JsonPropertyName("types")]
- public List<string>? Types { get; set; }
- }
+ [JsonPropertyName("include_redundant_members")]
+ public bool? IncludeRedundantMembers { get; set; }
+
+ [JsonPropertyName("lazy_load_members")]
+ public bool? LazyLoadMembers { get; set; }
+
+ [JsonPropertyName("rooms")]
+ public List<string>? Rooms { get; set; }
+
+ [JsonPropertyName("not_rooms")]
+ public List<string>? NotRooms { get; set; }
- [JsonPropertyName("timeline")]
- public TimelineFilter? Timeline { get; set; }
+ [JsonPropertyName("unread_thread_notifications")]
+ public bool? UnreadThreadNotifications { get; set; }
+ }
+ }
- public class TimelineFilter {
+ public class EventFilter {
[JsonPropertyName("limit")]
public int? Limit { get; set; }
@@ -53,10 +56,11 @@ public class RoomFilter {
[JsonPropertyName("not_types")]
public List<string>? NotTypes { get; set; }
+
+ [JsonPropertyName("senders")]
+ public List<string>? Senders { get; set; }
+
+ [JsonPropertyName("not_senders")]
+ public List<string>? NotSenders { get; set; }
}
}
-
-public class AccountDataFilter {
- [JsonPropertyName("not_types")]
- public List<string>? NotTypes { get; set; }
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
index be78a97..540a323 100644
--- a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
+++ b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
@@ -31,7 +31,7 @@ public class CreateRoomRequest {
public string Visibility { get; set; } = null!;
[JsonPropertyName("power_level_content_override")]
- public PowerLevelEventData PowerLevelContentOverride { get; set; } = null!;
+ public RoomPowerLevelEventData PowerLevelContentOverride { get; set; } = null!;
[JsonPropertyName("creation_content")]
public JsonObject CreationContent { get; set; } = new();
diff --git a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
index 422a557..a7f9187 100644
--- a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
@@ -1,3 +1,4 @@
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using MatrixRoomUtils.Core.Interfaces;
@@ -25,20 +26,23 @@ public class StateEventResponse : StateEvent {
[JsonPropertyName("replaces_state")]
public string ReplacesState { get; set; }
- [JsonPropertyName("prev_content")]
- public dynamic PrevContent { get; set; }
-
public class UnsignedData {
[JsonPropertyName("age")]
public ulong? Age { get; set; }
- [JsonPropertyName("prev_content")]
- public dynamic? PrevContent { get; set; }
-
[JsonPropertyName("redacted_because")]
- public dynamic? RedactedBecause { get; set; }
+ public object? RedactedBecause { get; set; }
[JsonPropertyName("transaction_id")]
public string? TransactionId { get; set; }
+
+ [JsonPropertyName("replaces_state")]
+ public string? ReplacesState { get; set; }
+
+ [JsonPropertyName("prev_sender")]
+ public string? PrevSender { get; set; }
+
+ [JsonPropertyName("prev_content")]
+ public JsonObject? PrevContent { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index 1c0a1cf..785c637 100644
--- a/MatrixRoomUtils.Core/StateEvent.cs
+++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -14,6 +14,17 @@ public class StateEvent {
public static List<Type> KnownStateEventTypes =
new ClassCollector<IStateEventType>().ResolveFromAllAccessibleAssemblies();
+ public static Type GetStateEventType(string type) {
+ if (type == "m.receipt") {
+ return typeof(Dictionary<string, JsonObject>);
+ }
+
+ var eventType = KnownStateEventTypes.FirstOrDefault(x =>
+ x.GetCustomAttributes<MatrixEventAttribute>()?.Any(y => y.EventName == type) ?? false);
+
+ return eventType ?? typeof(object);
+ }
+
public object TypedContent {
get {
try {
@@ -21,8 +32,9 @@ public class StateEvent {
}
catch (JsonException e) {
Console.WriteLine(e);
- Console.WriteLine("Content:\n"+ObjectExtensions.ToJson(RawContent));
+ Console.WriteLine("Content:\n" + ObjectExtensions.ToJson(RawContent));
}
+
return null;
}
set => RawContent = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value));
@@ -65,12 +77,7 @@ public class StateEvent {
[JsonIgnore]
public Type GetType {
get {
- if (Type == "m.receipt") {
- return typeof(Dictionary<string, JsonObject>);
- }
-
- var type = KnownStateEventTypes.FirstOrDefault(x =>
- x.GetCustomAttributes<MatrixEventAttribute>()?.Any(y => y.EventName == Type) ?? false);
+ var type = GetStateEventType(Type);
//special handling for some types
// if (type == typeof(RoomEmotesEventData)) {
@@ -94,7 +101,7 @@ public class StateEvent {
// }
// }
- return type ?? typeof(object);
+ return type;
}
}
diff --git a/MatrixRoomUtils.Core/StateEventStruct.cs b/MatrixRoomUtils.Core/StateEventStruct.cs
deleted file mode 100644
index cd301ac..0000000
--- a/MatrixRoomUtils.Core/StateEventStruct.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-namespace MatrixRoomUtils.Core;
-
-public struct StateEventStruct {
- public object content { get; set; }
- public long origin_server_ts { get; set; }
- public string sender { get; set; }
- public string state_key { get; set; }
- public string type { get; set; }
- public string event_id { get; set; }
- public string user_id { get; set; }
- public string replaces_state { get; set; }
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/Common/RoomEmotesEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/Common/RoomEmotesEventData.cs
index 633998c..c263a3a 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/Common/RoomEmotesEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/Common/RoomEmotesEventData.cs
@@ -2,25 +2,25 @@ using System.Text.Json.Serialization;
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Interfaces;
-namespace MatrixRoomUtils.Core.StateEventTypes.Common;
+namespace MatrixRoomUtils.Core.StateEventTypes.Common;
[MatrixEvent(EventName = "im.ponies.room_emotes")]
public class RoomEmotesEventData : IStateEventType {
[JsonPropertyName("emoticons")]
public Dictionary<string, EmoticonData>? Emoticons { get; set; }
-
+
[JsonPropertyName("images")]
public Dictionary<string, EmoticonData>? Images { get; set; }
-
+
[JsonPropertyName("pack")]
public PackInfo? Pack { get; set; }
-
+
public class EmoticonData {
[JsonPropertyName("url")]
public string? Url { get; set; }
}
-}
-public class PackInfo {
-
-}
\ No newline at end of file
+ public class PackInfo {
+
+ }
+}
diff --git a/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAvatarEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAvatarEventData.cs
index bab297b..a14e4c5 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAvatarEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAvatarEventData.cs
@@ -2,17 +2,27 @@ using System.Text.Json.Serialization;
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Interfaces;
-namespace MatrixRoomUtils.Core.StateEventTypes.Spec;
+namespace MatrixRoomUtils.Core.StateEventTypes.Spec;
[MatrixEvent(EventName = "m.room.avatar")]
public class RoomAvatarEventData : IStateEventType {
[JsonPropertyName("url")]
public string? Url { get; set; }
-
+
[JsonPropertyName("info")]
public RoomAvatarInfo? Info { get; set; }
public class RoomAvatarInfo {
-
+ [JsonPropertyName("h")]
+ public int? Height { get; set; }
+
+ [JsonPropertyName("w")]
+ public int? Width { get; set; }
+
+ [JsonPropertyName("mimetype")]
+ public string? MimeType { get; set; }
+
+ [JsonPropertyName("size")]
+ public int? Size { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomCreateEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomCreateEventData.cs
index 8b85d69..6127028 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomCreateEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomCreateEventData.cs
@@ -2,7 +2,7 @@ using System.Text.Json.Serialization;
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Interfaces;
-namespace MatrixRoomUtils.Core.StateEventTypes.Spec;
+namespace MatrixRoomUtils.Core.StateEventTypes.Spec;
[MatrixEvent(EventName = "m.room.create")]
public class RoomCreateEventData : IStateEventType {
@@ -16,6 +16,12 @@ public class RoomCreateEventData : IStateEventType {
public RoomCreatePredecessor? Predecessor { get; set; }
[JsonPropertyName("type")]
public string? Type { get; set; }
-
- public class RoomCreatePredecessor { }
-}
\ No newline at end of file
+
+ public class RoomCreatePredecessor {
+ [JsonPropertyName("room_id")]
+ public string? RoomId { get; set; }
+
+ [JsonPropertyName("event_id")]
+ public string? EventId { get; set; }
+ }
+}
diff --git a/MatrixRoomUtils.Core/StateEventTypes/Spec/PowerLevelEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomPowerLevelEventData.cs
index 6846db4..1cde660 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/Spec/PowerLevelEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomPowerLevelEventData.cs
@@ -5,7 +5,7 @@ using MatrixRoomUtils.Core.Interfaces;
namespace MatrixRoomUtils.Core.StateEventTypes.Spec;
[MatrixEvent(EventName = "m.room.power_levels")]
-public class PowerLevelEventData : IStateEventType {
+public class RoomPowerLevelEventData : IStateEventType {
[JsonPropertyName("ban")]
public int Ban { get; set; } // = 50;
@@ -35,14 +35,22 @@ public class PowerLevelEventData : IStateEventType {
[JsonPropertyName("users_default")]
public int UsersDefault { get; set; } // = 0;
-
+
[Obsolete("Historical was a key related to MSC2716, a spec change on backfill that was dropped!", true)]
[JsonIgnore]
[JsonPropertyName("historical")]
public int Historical { get; set; } // = 50;
-
+
public class NotificationsPL {
[JsonPropertyName("room")]
public int Room { get; set; } = 50;
}
-}
\ No newline at end of file
+
+ public bool IsUserAdmin(string userId) {
+ return Users.TryGetValue(userId, out var level) && level >= Events.Max(x=>x.Value);
+ }
+
+ public bool UserHasPermission(string userId, string eventType) {
+ return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault);
+ }
+}
diff --git a/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs b/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
index a519977..f895173 100644
--- a/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
+++ b/MatrixRoomUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs
@@ -48,7 +48,7 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate {
}
},
Visibility = "public",
- PowerLevelContentOverride = new PowerLevelEventData {
+ PowerLevelContentOverride = new RoomPowerLevelEventData {
UsersDefault = 0,
EventsDefault = 100,
StateDefault = 50,
@@ -56,7 +56,7 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate {
Redact = 50,
Kick = 50,
Ban = 50,
- NotificationsPl = new PowerLevelEventData.NotificationsPL {
+ NotificationsPl = new RoomPowerLevelEventData.NotificationsPL {
Room = 50
},
Events = new Dictionary<string, int> {
diff --git a/MatrixRoomUtils.Web/Classes/RoomInfo.cs b/MatrixRoomUtils.Web/Classes/RoomInfo.cs
index 5ecc431..f9d5452 100644
--- a/MatrixRoomUtils.Web/Classes/RoomInfo.cs
+++ b/MatrixRoomUtils.Web/Classes/RoomInfo.cs
@@ -1,13 +1,14 @@
using MatrixRoomUtils.Core;
+using MatrixRoomUtils.Core.Interfaces;
using MatrixRoomUtils.Core.Responses;
using MatrixRoomUtils.Core.RoomTypes;
-namespace MatrixRoomUtils.Web.Classes;
+namespace MatrixRoomUtils.Web.Classes;
public class RoomInfo {
public GenericRoom Room { get; set; }
public List<StateEventResponse?> StateEvents { get; init; } = new();
-
+
public async Task<StateEventResponse?> GetStateEvent(string type, string stateKey = "") {
var @event = StateEvents.FirstOrDefault(x => x.Type == type && x.StateKey == stateKey);
if (@event is not null) return @event;
@@ -23,7 +24,8 @@ public class RoomInfo {
if (e is { ErrorCode: "M_NOT_FOUND" }) @event.TypedContent = default!;
else throw;
}
+
StateEvents.Add(@event);
return @event;
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
index 13a120c..4b874c9 100644
--- a/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
+++ b/MatrixRoomUtils.Web/MatrixRoomUtils.Web.csproj
@@ -11,7 +11,6 @@
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.7" PrivateAssets="all" />
- <PackageReference Include="XtermBlazor" Version="1.9.0" />
</ItemGroup>
<ItemGroup>
diff --git a/MatrixRoomUtils.Web/Pages/About.razor b/MatrixRoomUtils.Web/Pages/About.razor
index 971bd9b..48c7686 100644
--- a/MatrixRoomUtils.Web/Pages/About.razor
+++ b/MatrixRoomUtils.Web/Pages/About.razor
@@ -3,7 +3,6 @@
@using System.Net.Sockets
@inject NavigationManager NavigationManager
@inject ILocalStorageService LocalStorage
-@using XtermBlazor
<PageTitle>About</PageTitle>
@@ -22,10 +21,6 @@
<p>This deployment also serves a copy of the compiled, hosting-ready binaries at <a href="MRU-SRC.tar.xz">/MRU-SRC.tar.xz</a>!</p>
}
-<Xterm @ref="_terminal" Options="_options" OnFirstRender="@OnFirstRender" style="max-width: fit-content; overflow-x: hidden;"/>
-
-
-
@code {
private bool showBinDownload { get; set; }
private bool showSrcDownload { get; set; }
@@ -39,29 +34,4 @@
await base.OnInitializedAsync();
}
-
- private Xterm _terminal;
-
- private TerminalOptions _options = new TerminalOptions
- {
- CursorBlink = true,
- CursorStyle = CursorStyle.Block,
- Theme =
- {
- Background = "#17615e",
- },
- };
-
- private async Task OnFirstRender() {
- var message = "Hello, World!\nThis is a terminal emulator!\n\nYou can type stuff here, and it will be sent to the server!\n\nThis is a test of the emergency broadcast system.\n\nThis is only a t";
- _terminal.Options.RendererType = RendererType.Dom;
- _terminal.Options.ScreenReaderMode = true;
-// TcpClient.
- for (var i = 0; i < message.Length; i++) {
- await _terminal.Write(message[i].ToString());
-
- await Task.Delay(50);
- _terminal.Options.Theme.Background = $"#{(i * 2):X6}";
- }
- }
}
diff --git a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
index 816299f..6d12dc2 100644
--- a/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Index.razor
@@ -13,45 +13,54 @@
@code {
+ public List<RoomInfo> KnownRooms { get; set; } = new();
+
private List<RoomInfo> Rooms { get; set; } = new();
private ProfileResponseEventData GlobalProfile { get; set; }
- protected override async Task OnInitializedAsync() {
- var hs = await MRUStorage.GetCurrentSessionOrNavigate();
- if (hs is null) return;
- GlobalProfile = await hs.GetProfile(hs.WhoAmI.UserId);
- var filter = new SyncFilter() {
+ private SyncFilter filter = new() {
+ AccountData = new() {
+ NotTypes = new() { "*" },
+ Limit = 1
+ },
+ Presence = new() {
+ NotTypes = new() { "*" },
+ Limit = 1
+ },
+ Room = new() {
AccountData = new() {
- NotTypes = new() { "*" }
+ NotTypes = new() { "*" },
+ Limit = 1
},
- Presence = new() {
- NotTypes = new() { "*" }
+ Ephemeral = new() {
+ NotTypes = new() { "*" },
+ Limit = 1
},
- Room = new RoomFilter() {
- AccountData = new() {
- NotTypes = new() { "*" }
- },
- Ephemeral = new() {
- NotTypes = new() { "*" }
- },
- State = new RoomFilter.StateFilter() {
- Types = new List<string>() {
- "m.room.name",
- "m.room.avatar",
- "m.room.create",
- "org.matrix.mjolnir.shortcode",
- }
- },
- Timeline = new() {
- NotTypes = new() { "*" },
- Limit = 1
+ State = new() {
+ Types = new List<string>() {
+ "m.room.name",
+ "m.room.avatar",
+ "m.room.create",
+ "org.matrix.mjolnir.shortcode",
+ "m.room.power_levels"
}
+ },
+ Timeline = new() {
+ NotTypes = new() { "*" },
+ Limit = 1
}
- };
+ }
+ };
+
+ protected override async Task OnInitializedAsync() {
+ var hs = await MRUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+ GlobalProfile = await hs.GetProfile(hs.WhoAmI.UserId);
+
Status = "Syncing...";
SyncResult? sync = null;
string? nextBatch = null;
- while (sync is null or { Rooms.Join.Count: > 10}) {
+ while (sync is null or { Rooms.Join.Count: >= 1}) {
sync = await hs.SyncHelper.Sync(since: nextBatch, filter: filter, timeout: 0);
nextBatch = sync?.NextBatch ?? nextBatch;
if (sync is null) continue;
@@ -70,11 +79,13 @@
StateEvents = new()
};
Rooms.Add(room);
+ KnownRooms.Add(room);
}
room.StateEvents.AddRange(roomData.State.Events);
}
- Status = $"Got {Rooms.Count} rooms so far!";
+ Status = $"Got {Rooms.Count} rooms so far! Next batch: {nextBatch}";
StateHasChanged();
+ await Task.Delay(100);
}
Console.WriteLine("Sync done!");
Status = "Sync complete!";
@@ -103,8 +114,10 @@
}
Console.WriteLine("Set stub data!");
Status = "Set stub data!";
+ SemaphoreSlim semaphore = new(8, 8);
var memberTasks = Rooms.Select(async roomInfo => {
if (!roomInfo.StateEvents.Any(x => x.Type == "m.room.member" && x.StateKey == hs.WhoAmI.UserId)) {
+ await semaphore.WaitAsync();
roomInfo.StateEvents.Add(new StateEventResponse() {
Type = "m.room.member",
StateKey = hs.WhoAmI.UserId,
@@ -112,6 +125,7 @@
Membership = "unknown"
}
});
+ semaphore.Release();
}
}).ToList();
await Task.WhenAll(memberTasks);
diff --git a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerSpace.razor b/MatrixRoomUtils.Web/Pages/Rooms/Space.razor
index afa39b9..91f97d0 100644
--- a/MatrixRoomUtils.Web/Pages/RoomManager/RoomManagerSpace.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/Space.razor
@@ -1,4 +1,4 @@
-@page "/RoomManager/Space/{RoomId}"
+@page "/Rooms/{RoomId}/Space"
@using System.Text.Json
@using MatrixRoomUtils.Core.Responses
<h3>Room manager - Viewing Space</h3>
@@ -93,4 +93,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor
index 8b2ff0c..8b2ff0c 100644
--- a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateEditorPage.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/StateEditor.razor
diff --git a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor b/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor
index 09b38f0..09b38f0 100644
--- a/MatrixRoomUtils.Web/Pages/RoomState/RoomStateViewerPage.razor
+++ b/MatrixRoomUtils.Web/Pages/Rooms/StateViewer.razor
diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
index 709f2d7..b798d49 100644
--- a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
+++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListCategory.razor
@@ -6,13 +6,14 @@
@foreach (var room in rooms) {
<div class="room-list-item">
<RoomListItem RoomInfo="@room" ShowOwnProfile="@(roomType == "Room")"></RoomListItem>
- @if (RoomVersionDangerLevel(room) != 0) {
- <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton Color="@(RoomVersionDangerLevel(room) == 2 ? "#ff0000" : "#ff8800")" href="@($"/Rooms/Create?Import={room.Room.RoomId}")">Upgrade room</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
- }
+ @* @if (RoomVersionDangerLevel(room) != 0 && *@
+ @* (room.StateEvents.FirstOrDefault(x=>x.Type == "m.room.power_levels")?.TypedContent is RoomPowerLevelEventData powerLevels && powerLevels.UserHasPermission(HomeServer.UserId, "m.room.tombstone"))) { *@
+ @* <MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton Color="@(RoomVersionDangerLevel(room) == 2 ? "#ff0000" : "#ff8800")" href="@($"/Rooms/Create?Import={room.Room.RoomId}")">Upgrade room</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton> *@
+ @* } *@
<MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.Room.RoomId}/Timeline")">View timeline</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
<MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.Room.RoomId}/State/View")">View state</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
<MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{room.Room.RoomId}/State/Edit")">Edit state</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
-
+
@if (roomType == "Space") {
<RoomListSpace Space="@room"></RoomListSpace>
}
@@ -25,10 +26,13 @@
[Parameter]
public KeyValuePair<string, List<RoomInfo>> Category { get; set; }
-
+
[Parameter]
public ProfileResponseEventData? GlobalProfile { get; set; }
+ [CascadingParameter]
+ public AuthenticatedHomeServer HomeServer { get; set; } = null!;
+
private string roomType => Category.Key;
private List<RoomInfo> rooms => Category.Value;
@@ -42,4 +46,4 @@
return 0;
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
index 5153658..a113f0b 100644
--- a/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
+++ b/MatrixRoomUtils.Web/Shared/RoomListComponents/RoomListSpace.razor
@@ -1,4 +1,4 @@
-<LinkButton href="@($"/Rooms/{Space.Room.RoomId}/Space")">Manage space</LinkButton>
+<MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton href="@($"/Rooms/{Space.Room.RoomId}/Space")">Manage space</MatrixRoomUtils.Web.Shared.SimpleComponents.LinkButton>
<br/>
<details @ontoggle="SpaceChildrenOpened">
@@ -17,6 +17,9 @@
public RoomInfo Space { get; set; }
[Parameter, CascadingParameter]
+ public List<RoomInfo> KnownRooms { get; set; } = new();
+
+ [Parameter, CascadingParameter]
public string? Breadcrumbs {
get => _breadcrumbs + Space.Room.RoomId;
set => _breadcrumbs = value;
@@ -30,9 +33,14 @@
var rooms = Space.Room.AsSpace.GetRoomsAsync();
await foreach (var room in rooms) {
if (Breadcrumbs.Contains(room.RoomId)) continue;
- Children.Add(new() {
- Room = room
- });
+ RoomInfo roomInfo = KnownRooms.FirstOrDefault(x => x.Room.RoomId == room.RoomId);
+ if (roomInfo is null) {
+ roomInfo = new() {
+ Room = room
+ };
+ KnownRooms.Add(roomInfo);
+ }
+ Children.Add(roomInfo);
}
await base.OnInitializedAsync();
}
@@ -46,4 +54,4 @@
Console.WriteLine($"[RoomList] Rendering children of {Space.Room.RoomId}");
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
index b89fb18..e12f622 100644
--- a/MatrixRoomUtils.Web/Shared/RoomListItem.razor
+++ b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
@@ -32,7 +32,7 @@
[Parameter]
public GenericRoom? Room { get; set; }
-
+
[Parameter]
public RoomInfo? RoomInfo { get; set; }
@@ -69,20 +69,32 @@
if (Room is null && RoomId is null && RoomInfo is null) {
throw new ArgumentNullException(nameof(RoomId));
}
-
+
// sweep from roominfo to id
if (RoomInfo is not null) Room = RoomInfo.Room;
if(Room is not null) RoomId = Room.RoomId;
-
+
//sweep from id to roominfo
if(RoomId is not null) Room ??= await hs.GetRoom(RoomId);
if(Room is not null) RoomInfo ??= new RoomInfo() {
Room = Room
};
- await CheckRoomVersion();
- await GetRoomInfo();
- await LoadOwnProfile();
+ try {
+ await CheckRoomVersion();
+ await GetRoomInfo();
+ await LoadOwnProfile();
+ }
+ catch (MatrixException e) {
+ if (e is not { ErrorCode: "M_FORBIDDEN" }) {
+ throw;
+ }
+ roomName = "Error: " + e.Message;
+ roomIcon = "/blobfox_outage.gif";
+ }
+ catch (Exception e) {
+ Console.WriteLine($"Failed to load room info for {RoomId}: {e.Message}");
+ }
_semaphoreSlim.Release();
}
@@ -104,24 +116,17 @@
}
private async Task CheckRoomVersion() {
- try {
- var ce = (await RoomInfo.GetStateEvent("m.room.create")).TypedContent as RoomCreateEventData;
- if (int.TryParse(ce.RoomVersion, out var rv)) {
- if (rv < 10)
- hasOldRoomVersion = true;
- }
- else // treat unstable room versions as dangerous
- hasDangerousRoomVersion = true;
-
- if (RoomConstants.DangerousRoomVersions.Contains(ce.RoomVersion)) {
- hasDangerousRoomVersion = true;
- roomName = "Dangerous room: " + roomName;
- }
+ var ce = (await RoomInfo.GetStateEvent("m.room.create")).TypedContent as RoomCreateEventData;
+ if (int.TryParse(ce.RoomVersion, out var rv)) {
+ if (rv < 10)
+ hasOldRoomVersion = true;
}
- catch (MatrixException e) {
- if (e is not { ErrorCode: "M_FORBIDDEN" }) {
- throw;
- }
+ else // treat unstable room versions as dangerous
+ hasDangerousRoomVersion = true;
+
+ if (RoomConstants.DangerousRoomVersions.Contains(ce.RoomVersion)) {
+ hasDangerousRoomVersion = true;
+ roomName = "Dangerous room: " + roomName;
}
}
@@ -132,7 +137,7 @@
var state = (await RoomInfo.GetStateEvent("m.room.avatar")).TypedContent as RoomAvatarEventData;
if (state?.Url is { } url) {
roomIcon = MediaResolver.ResolveMediaUri(hs.FullHomeServerDomain, url);
- Console.WriteLine($"Got avatar for room {RoomId}: {roomIcon} ({url})");
+ // Console.WriteLine($"Got avatar for room {RoomId}: {roomIcon} ({url})");
}
}
catch (MatrixException e) {
@@ -142,4 +147,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/MatrixRoomUtils.Web/wwwroot/blobfox_outage.gif b/MatrixRoomUtils.Web/wwwroot/blobfox_outage.gif
new file mode 100644
index 0000000..6f1e2ae
--- /dev/null
+++ b/MatrixRoomUtils.Web/wwwroot/blobfox_outage.gif
Binary files differdiff --git a/MatrixRoomUtils.Web/wwwroot/index.html b/MatrixRoomUtils.Web/wwwroot/index.html
index 9a85530..0439e62 100644
--- a/MatrixRoomUtils.Web/wwwroot/index.html
+++ b/MatrixRoomUtils.Web/wwwroot/index.html
@@ -10,8 +10,6 @@
<link href="css/app.css" rel="stylesheet"/>
<link href="favicon.png" rel="icon" type="image/png"/>
<link href="MatrixRoomUtils.Web.styles.css" rel="stylesheet"/>
- <link href="_content/XtermBlazor/XtermBlazor.css" rel="stylesheet" />
- <script src="_content/XtermBlazor/XtermBlazor.min.js"></script>
</head>
<body>
diff --git a/MatrixRoomUtils.sln.DotSettings.user b/MatrixRoomUtils.sln.DotSettings.user
index 785af7c..8e084e1 100644
--- a/MatrixRoomUtils.sln.DotSettings.user
+++ b/MatrixRoomUtils.sln.DotSettings.user
@@ -6,8 +6,8 @@
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/SolutionBuilderNext/ParallelProcessesCount2/@EntryValue">12</s:Int64>
<s:Boolean x:Key="/Default/Environment/Hierarchy/Build/SolutionBuilderNext/ShouldRestoreNugetPackages/@EntryValue">True</s:Boolean>
- <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=244e90fe_002Dee26_002D4f78_002D86eb_002D27529ae48905_0023MatrixRoomUtils/@EntryIndexedValue">True</s:Boolean>
- <s:Boolean x:Key="/Default/UnloadedProject/UnloadedProjects/=f997f26f_002D2ec1_002D4d18_002Db3dd_002Dc46fb2ad65c0_0023MatrixRoomUtils_002EWeb_002EServer/@EntryIndexedValue">True</s:Boolean>
+
+
diff --git a/README.MD b/README.MD
index 7bf6942..4c0a94b 100644
--- a/README.MD
+++ b/README.MD
@@ -11,3 +11,15 @@ git format-patch --output-directory "./patches" @{u}..
# Send patches
```
+
+### Developer utility commands
+
+Error reporting upon file save:
+```sh
+inotifywait -rmqe CLOSE_WRITE --include '.*\.cs$' . | while read l; do clear; dotnet build --property WarningLevel=0; done
+```
+
+Hot rebuild on file save:
+```sh
+dotnet watch run --no-hot-reload --property WarningLevel=0
+```
|