commit f143c8cd3adc23a8f4473fc7cea7d1c58322233b
parent e3e4b10cc85decc832117717af8a4f4d9f6f728d
Author: TheArcaneBrony <myrainbowdash949@gmail.com>
Date: Tue, 23 May 2023 17:19:17 +0200
idk
Diffstat:
5 files changed, 216 insertions(+), 25 deletions(-)
diff --git a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
@@ -0,0 +1,22 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core.Extensions;
+
+public static class JsonElementExtensions
+{
+ public static void FindExtraJsonFields([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}!");
+ unknownPropertyFound = true;
+ }
+ if(unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
+ }
+}
+\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
@@ -1,6 +1,10 @@
+using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
+using System.Reflection;
using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Web;
+using MatrixRoomUtils.Core.Extensions;
namespace MatrixRoomUtils.Core;
@@ -21,16 +25,22 @@ public class Room
{
await _semaphore.WaitAsync();
var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
- if (!string.IsNullOrEmpty(state_key)) url += $"/{type}/{state_key}";
- else if (!string.IsNullOrEmpty(type)) url += $"/{type}";
- var cache_key = "room_states:" + type;
+ var stateCombo = "";
+ if (!string.IsNullOrEmpty(state_key)) stateCombo += $"{type}/{state_key}";
+ else if (!string.IsNullOrEmpty(type)) stateCombo += $"{type}";
+ if (!string.IsNullOrEmpty(stateCombo)) url += $"/{stateCombo}";
+ var cache_key = "room_states#" + RoomId;
if (!RuntimeCache.GenericResponseCache.ContainsKey(cache_key))
{
Console.WriteLine($"[!!] No cache for {cache_key}, creating...");
- RuntimeCache.GenericResponseCache.Add(cache_key, new ObjectCache<object?>());
+ RuntimeCache.GenericResponseCache.Add(cache_key, new ObjectCache<object?>()
+ {
+ Name = cache_key
+ });
}
+ var cache = RuntimeCache.GenericResponseCache[cache_key];
- RuntimeCache.GenericResponseCache[cache_key].DefaultExpiry = type switch
+ cache.DefaultExpiry = type switch
{
"m.room.name" => TimeSpan.FromMinutes(30),
"org.matrix.mjolnir.shortcode" => TimeSpan.FromHours(4),
@@ -38,17 +48,18 @@ public class Room
_ => TimeSpan.FromMinutes(15)
};
- if (RuntimeCache.GenericResponseCache[cache_key].Cache.ContainsKey(url) && RuntimeCache.GenericResponseCache[cache_key][url] != null)
+ if (cache.ContainsKey(stateCombo))
{
- if (RuntimeCache.GenericResponseCache[cache_key][url].ExpiryTime > DateTime.Now)
+ if (cache[stateCombo].ExpiryTime > DateTime.Now)
{
// Console.WriteLine($"[:3] Found cached state: {RuntimeCache.GenericResponseCache[cache_key][url].Result}");
_semaphore.Release();
- return (JsonElement?)RuntimeCache.GenericResponseCache[cache_key][url].Result;
+ return (JsonElement?) cache[stateCombo].Result;
}
else
{
- Console.WriteLine($"[!!] Cached state expired at {RuntimeCache.GenericResponseCache[cache_key][url].ExpiryTime}: {RuntimeCache.GenericResponseCache[cache_key][url].Result}");
+ Console.WriteLine($"[!!] Cached state expired at {cache[stateCombo].ExpiryTime}: {cache[stateCombo].Result}");
+ if(cache[stateCombo].ExpiryTime == null)Console.WriteLine("Exiryt time was null");
}
}
// else
@@ -66,13 +77,7 @@ public class Room
var result = await res.Content.ReadFromJsonAsync<JsonElement>();
- if (!RuntimeCache.GenericResponseCache.ContainsKey(cache_key) && type != "")
- {
- Console.WriteLine($"[!!] No cache for {cache_key}, creating...");
- RuntimeCache.GenericResponseCache.Add(cache_key, new ObjectCache<object?>());
- }
-
- RuntimeCache.GenericResponseCache[cache_key][url] = new GenericResult<object>()
+ cache[stateCombo] = new GenericResult<object>()
{
Result = result
};
@@ -102,4 +107,111 @@ public class Room
var full_join_url = $"{join_url}?server_name=" + string.Join("&server_name=", homeservers);
var res = await _httpClient.PostAsync(full_join_url, null);
}
+
+ public async Task<List<string>> GetMembersAsync()
+ {
+ var res = await GetStateAsync("");
+ if (!res.HasValue) return new List<string>();
+ var members = new List<string>();
+ foreach (var member in res.Value.EnumerateArray())
+ {
+ if(member.GetProperty("type").GetString() != "m.room.member") continue;
+ var member_id = member.GetProperty("state_key").GetString();
+ members.Add(member_id);
+ }
+
+ return members;
+ }
+
+ public async Task<List<string>> GetAliasesAsync()
+ {
+ var res = await GetStateAsync("m.room.aliases");
+ if (!res.HasValue) return new List<string>();
+ var aliases = new List<string>();
+ foreach (var alias in res.Value.GetProperty("aliases").EnumerateArray())
+ {
+ aliases.Add(alias.GetString() ?? "");
+ }
+
+ return aliases;
+ }
+
+ public async Task<string> GetCanonicalAliasAsync()
+ {
+ var res = await GetStateAsync("m.room.canonical_alias");
+ if (!res.HasValue) return "";
+ return res.Value.GetProperty("alias").GetString() ?? "";
+ }
+
+ public async Task<string> GetTopicAsync()
+ {
+ var res = await GetStateAsync("m.room.topic");
+ if (!res.HasValue) return "";
+ return res.Value.GetProperty("topic").GetString() ?? "";
+ }
+
+ public async Task<string> GetAvatarUrlAsync()
+ {
+ var res = await GetStateAsync("m.room.avatar");
+ if (!res.HasValue) return "";
+ return res.Value.GetProperty("url").GetString() ?? "";
+ }
+
+ public async Task<JoinRules> GetJoinRuleAsync()
+ {
+ var res = await GetStateAsync("m.room.join_rules");
+ if (!res.HasValue) return new JoinRules();
+ return res.Value.Deserialize<JoinRules>() ?? new JoinRules();
+ }
+
+ public async Task<string> GetHistoryVisibilityAsync()
+ {
+ var res = await GetStateAsync("m.room.history_visibility");
+ if (!res.HasValue) return "";
+ return res.Value.GetProperty("history_visibility").GetString() ?? "";
+ }
+
+ public async Task<string> GetGuestAccessAsync()
+ {
+ var res = await GetStateAsync("m.room.guest_access");
+ if (!res.HasValue) return "";
+ return res.Value.GetProperty("guest_access").GetString() ?? "";
+ }
+
+ public async Task<CreateEvent> GetCreateEventAsync()
+ {
+ var res = await GetStateAsync("m.room.create");
+ if (!res.HasValue) return new CreateEvent();
+
+ res.FindExtraJsonFields(typeof(CreateEvent));
+
+ return res.Value.Deserialize<CreateEvent>() ?? new CreateEvent();
+ }
+}
+
+public class CreateEvent
+{
+ [JsonPropertyName("creator")]
+ public string Creator { get; set; }
+ [JsonPropertyName("room_version")]
+ public string RoomVersion { get; set; }
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+ [JsonPropertyName("predecessor")]
+ public object? Predecessor { get; set; }
+
+ [JsonPropertyName("m.federate")]
+ public bool Federate { get; set; }
+}
+
+public class JoinRules
+{
+ private const string Public = "public";
+ private const string Invite = "invite";
+ private const string Knock = "knock";
+
+ [JsonPropertyName("join_rule")]
+ public string JoinRule { get; set; }
+ [JsonPropertyName("allow")]
+ public List<string> Allow { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -14,6 +14,15 @@ public class RuntimeCache
// public static Dictionary<string, (DateTime cachedAt, ProfileResponse response)> ProfileCache { get; set; } = new();
public static Dictionary<string, ObjectCache<object>> GenericResponseCache { get; set; } = new();
+
+ public static Action Save { get; set; } = () =>
+ {
+ Console.WriteLine("RuntimeCache.Save() was called, but no callback was set!");
+ };
+ public static Action<string, object> SaveObject { get; set; } = (key, value) =>
+ {
+ Console.WriteLine($"RuntimeCache.SaveObject({key}, {value}) was called, but no callback was set!");
+ };
}
@@ -32,17 +41,18 @@ public class HomeServerResolutionResult
public class ObjectCache<T> where T : class
{
public Dictionary<string, GenericResult<T>> Cache { get; set; } = new();
- public TimeSpan DefaultExpiry { get; set; } = new(0, 5, 0);
+ public TimeSpan DefaultExpiry { get; set; } = new(0, 0, 0);
+ public string Name { get; set; } = null!;
public GenericResult<T> this[string key]
{
get
{
if (Cache.ContainsKey(key))
{
+ Console.WriteLine($"cache.get({key}): hit");
// Console.WriteLine($"Found item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
if(Cache[key].ExpiryTime > DateTime.Now)
return Cache[key];
-
Console.WriteLine($"Expired item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
try
{
@@ -53,13 +63,15 @@ public class ObjectCache<T> where T : class
Console.WriteLine($"Failed to remove {key} from cache: {e.Message}");
}
}
- Console.WriteLine($"No item in cache: {key}");
+ Console.WriteLine($"cache.get({key}): miss");
return null;
}
set
{
Cache[key] = value;
if(Cache[key].ExpiryTime == null) Cache[key].ExpiryTime = DateTime.Now.Add(DefaultExpiry);
+ Console.WriteLine($"set({key}) = {Cache[key].Result.ToJson(indent:false)}");
+ Console.WriteLine($"new_state: {this.ToJson(indent:false)}");
// Console.WriteLine($"New item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
// Console.Error.WriteLine("Full cache: " + Cache.ToJson());
}
@@ -78,14 +90,17 @@ public class ObjectCache<T> where T : class
// Console.WriteLine($"Removing {x.Key} from cache");
Cache.Remove(x.Key);
}
+ RuntimeCache.SaveObject("rory.matrixroomutils.generic_cache:" + Name, this);
}
});
}
+
+ public bool ContainsKey(string key) => Cache.ContainsKey(key);
}
public class GenericResult<T>
{
public T? Result { get; set; }
- public DateTime? ExpiryTime { get; set; }
+ public DateTime? ExpiryTime { get; set; } = DateTime.Now;
public GenericResult()
{
diff --git a/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs b/MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs
@@ -16,6 +16,12 @@ public partial class LocalStorageWrapper
public static async Task LoadFromLocalStorage(ILocalStorageService localStorage)
{
Settings = await localStorage.GetItemAsync<Settings>("rory.matrixroomutils.settings") ?? new();
+
+ //RuntimeCache stuff
+ async void Save() => await SaveToLocalStorage(localStorage);
+
+ RuntimeCache.Save = Save;
+ RuntimeCache.SaveObject = async (key, obj) => await localStorage.SetItemAsync(key, obj);
// RuntimeCache.AccessToken = await localStorage.GetItemAsync<string>("rory.matrixroomutils.token");
RuntimeCache.LastUsedToken = await localStorage.GetItemAsync<string>("rory.matrixroomutils.last_used_token");
// RuntimeCache.CurrentHomeserver = await localStorage.GetItemAsync<string>("rory.matrixroomutils.current_homeserver");
@@ -31,6 +37,12 @@ public partial class LocalStorageWrapper
Console.WriteLine("Created authenticated home server");
}
RuntimeCache.GenericResponseCache = await localStorage.GetItemAsync<Dictionary<string, ObjectCache<object>>>("rory.matrixroomutils.generic_cache") ?? new();
+
+ foreach (var s in (await localStorage.KeysAsync()).Where(x=>x.StartsWith("rory.matrixroomutils.generic_cache:")).ToList())
+ {
+ Console.WriteLine($"Loading generic cache entry {s}");
+ RuntimeCache.GenericResponseCache[s.Replace("rory.matrixroomutils.generic_cache:", "")] = await localStorage.GetItemAsync<ObjectCache<object>>(s);
+ }
RuntimeCache.WasLoaded = true;
}
@@ -45,6 +57,10 @@ public partial class LocalStorageWrapper
RuntimeCache.HomeserverResolutionCache.DistinctBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Value));
await localStorage.SetItemAsync("rory.matrixroomutils.generic_cache", RuntimeCache.GenericResponseCache);
+ // foreach (var s in RuntimeCache.GenericResponseCache.Keys)
+ // {
+ // await localStorage.SetItemAsync($"rory.matrixroomutils.generic_cache:{s}", RuntimeCache.GenericResponseCache[s]);
+ // }
}
public static async Task SaveFieldToLocalStorage(ILocalStorageService localStorage, string key)
{
@@ -55,7 +71,6 @@ public partial class LocalStorageWrapper
if (key == "rory.matrixroomutils.last_used_token") await localStorage.SetItemAsync(key, RuntimeCache.LastUsedToken);
if (key == "rory.matrixroomutils.homeserver_resolution_cache") await localStorage.SetItemAsync(key, RuntimeCache.HomeserverResolutionCache);
if (key == "rory.matrixroomutils.generic_cache") await localStorage.SetItemAsync(key, RuntimeCache.GenericResponseCache);
-
}
}
diff --git a/MatrixRoomUtils.Web/Shared/RoomListItem.razor b/MatrixRoomUtils.Web/Shared/RoomListItem.razor
@@ -1,6 +1,7 @@
@using MatrixRoomUtils.Core.Authentication
@using System.Text.Json
-<div style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-content;">
+@using MatrixRoomUtils.Core.Extensions
+<div style="background-color: #ffffff11; border-radius: 25px; margin: 8px; width: fit-content; @(hasDangerousRoomVersion ? "border: red 4px solid;" : hasOldRoomVersion ? "border: #FF0 1px solid;" : "")">
@if (ShowOwnProfile)
{
<img style="@(ChildContent != null ? "vertical-align: baseline;":"") width: 32px; height: 32px; border-radius: 50%; @(hasCustomProfileAvatar ? "border-color: red; border-width: 3px; border-style: dashed;" : "")" src="@profileAvatar"/>
@@ -39,6 +40,9 @@
private string profileName { get; set; } = "Loading...";
private bool hasCustomProfileAvatar { get; set; } = false;
private bool hasCustomProfileName { get; set; } = false;
+
+ private bool hasOldRoomVersion { get; set; } = false;
+ private bool hasDangerousRoomVersion { get; set; } = false;
protected override async Task OnInitializedAsync()
{
@@ -69,13 +73,35 @@
roomName = "Unnamed room: " + RoomId;
}
+ var ce = await Room.GetCreateEventAsync();
+ if (ce != null)
+ {
+ if (int.TryParse(ce.RoomVersion, out int rv) && rv < 10)
+ {
+ hasOldRoomVersion = true;
+ }
+ if (new[] { "1", "8" }.Contains(ce.RoomVersion))
+ {
+ hasDangerousRoomVersion = true;
+ roomName = "Dangerous room: " + roomName;
+ }
+ }
+
+
var state = await Room.GetStateAsync("m.room.avatar");
if (state != null)
{
- var url = state.Value.GetProperty("url").GetString();
- if (url != null)
+ try
+ {
+ var url = state.Value.GetProperty("url").GetString();
+ if (url != null)
+ {
+ roomIcon = await RuntimeCache.CurrentHomeServer.ResolveMediaUri(url);
+ }
+ }
+ catch (InvalidOperationException e)
{
- roomIcon = await RuntimeCache.CurrentHomeServer.ResolveMediaUri(url);
+ Console.WriteLine($"Failed to get avatar for room {RoomId}: {e.Message}\n{state.Value.ToJson()}");
}
}