about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTheArcaneBrony <myrainbowdash949@gmail.com>2023-05-23 17:19:17 +0200
committerTheArcaneBrony <myrainbowdash949@gmail.com>2023-05-23 17:19:17 +0200
commitf143c8cd3adc23a8f4473fc7cea7d1c58322233b (patch)
tree0aca8c6b5c694e46d3b713b965e35f7d9e89c322
parentPrevent deploy without commit (diff)
downloadMatrixUtils-f143c8cd3adc23a8f4473fc7cea7d1c58322233b.tar.xz
idk
-rw-r--r--MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs22
-rw-r--r--MatrixRoomUtils.Core/Room.cs144
-rw-r--r--MatrixRoomUtils.Core/RuntimeCache.cs23
-rw-r--r--MatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs17
-rw-r--r--MatrixRoomUtils.Web/Shared/RoomListItem.razor34
5 files changed, 215 insertions, 25 deletions
diff --git a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
new file mode 100644
index 0000000..725c832
--- /dev/null
+++ 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
index 6798d5b..362abf4 100644
--- 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
index 4a96f50..4e756a7 100644
--- 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
index c224160..4a00a8a 100644
--- 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
index 16ced75..317d25a 100644
--- 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()}");
             }
         }