MatrixRoomUtils

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | LICENSE

commit f143c8cd3adc23a8f4473fc7cea7d1c58322233b
parent e3e4b10cc85decc832117717af8a4f4d9f6f728d
Author: TheArcaneBrony <myrainbowdash949@gmail.com>
Date:   Tue, 23 May 2023 17:19:17 +0200

idk

Diffstat:
AMatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs | 23+++++++++++++++++++++++
MMatrixRoomUtils.Core/Room.cs | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
MMatrixRoomUtils.Core/RuntimeCache.cs | 23+++++++++++++++++++----
MMatrixRoomUtils.Web/Classes/LocalStorageWrapper.cs | 17++++++++++++++++-
MMatrixRoomUtils.Web/Shared/RoomListItem.razor | 34++++++++++++++++++++++++++++++----
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()}"); } }