about summary refs log tree commit diff
path: root/MatrixRoomUtils.Core
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixRoomUtils.Core')
-rw-r--r--MatrixRoomUtils.Core/AuthenticatedHomeServer.cs12
-rw-r--r--MatrixRoomUtils.Core/Authentication/MatrixAuth.cs3
-rw-r--r--MatrixRoomUtils.Core/Interfaces/IHomeServer.cs2
-rw-r--r--MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs217
-rw-r--r--MatrixRoomUtils.Core/Responses/StateEventResponse.cs34
-rw-r--r--MatrixRoomUtils.Core/Room.cs34
-rw-r--r--MatrixRoomUtils.Core/RuntimeCache.cs36
-rw-r--r--MatrixRoomUtils.Core/StateEvent.cs71
8 files changed, 335 insertions, 74 deletions
diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs

index 7a1f5de..502fe5b 100644 --- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs +++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -59,6 +59,18 @@ public class AuthenticatedHomeServer : IHomeServer return rooms; } + public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") + { + var res = await _httpClient.PostAsync($"/_matrix/media/r0/upload?filename={fileName}", new StreamContent(fileStream)); + if (!res.IsSuccessStatusCode) + { + Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}"); + throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}"); + } + var resJson = await res.Content.ReadFromJsonAsync<JsonElement>(); + return resJson.GetProperty("content_uri").GetString()!; + } + diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
index 7bcd75f..b4b8d19 100644 --- a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs +++ b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
@@ -1,5 +1,6 @@ using System.Net.Http.Json; using System.Text.Json; +using MatrixRoomUtils.Core.Extensions; using MatrixRoomUtils.Core.Responses; namespace MatrixRoomUtils.Core.Authentication; @@ -33,7 +34,7 @@ public class MatrixAuth await Task.Delay(retryAfter.GetInt32()); return await Login(homeserver, username, password); } - + Console.WriteLine($"Login: {data.ToJson()}"); return data.Deserialize<LoginResponse>(); //var token = data.GetProperty("access_token").GetString(); //return token; diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index 8fb8b2c..9f7bfee 100644 --- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs +++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -108,7 +108,7 @@ public class IHomeServer _profileCache[mxid] = profile; return profile; } - public async Task<string> ResolveMediaUri(string mxc) + public string ResolveMediaUri(string mxc) { return mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/r0/download/"); } diff --git a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs new file mode 100644
index 0000000..6949b1a --- /dev/null +++ b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
@@ -0,0 +1,217 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +namespace MatrixRoomUtils.Core.Responses; + +public class CreateRoomRequest +{ + [JsonPropertyName("name")] public string Name { get; set; } = null!; + + [JsonPropertyName("room_alias_name")] public string RoomAliasName { get; set; } = null!; + + //we dont want to use this, we want more control + // [JsonPropertyName("preset")] + // public string Preset { get; set; } = null!; + [JsonPropertyName("initial_state")] public List<StateEvent> InitialState { get; set; } = null!; + [JsonPropertyName("visibility")] public string Visibility { get; set; } = null!; + + [JsonPropertyName("power_level_content_override")] + public PowerLevelEvent PowerLevelContentOverride { get; set; } = null!; + + [JsonPropertyName("creation_content")] public JsonObject CreationContent { get; set; } = new(); + + /// <summary> + /// For use only when you can't use the CreationContent property + /// </summary> + + + //extra properties + [JsonIgnore] + public string HistoryVisibility + { + get + { + var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.history_visibility"); + if (stateEvent == null) + { + InitialState.Add(new StateEvent() + { + Type = "m.room.history_visibility", + Content = new JsonObject() + { + ["history_visibility"] = "shared" + } + }); + return "shared"; + } + + return stateEvent.ContentAsJsonNode["history_visibility"].GetValue<string>(); + } + set + { + var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.history_visibility"); + if (stateEvent == null) + { + InitialState.Add(new StateEvent() + { + Type = "m.room.history_visibility", + Content = new JsonObject() + { + ["history_visibility"] = value + } + }); + } + else + { + var v = stateEvent.ContentAsJsonNode; + v["history_visibility"] = value; + stateEvent.ContentAsJsonNode = v; + } + } + } + + [JsonIgnore] + public string RoomIcon + { + get + { + var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.avatar"); + if (stateEvent == null) + { + InitialState.Add(new StateEvent() + { + Type = "m.room.avatar", + Content = new JsonObject() + { + ["url"] = "" + } + }); + return ""; + } + + return stateEvent.ContentAsJsonNode["url"].GetValue<string>(); + } + set + { + var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.avatar"); + if (stateEvent == null) + { + InitialState.Add(new StateEvent() + { + Type = "m.room.avatar", + Content = new JsonObject() + { + ["url"] = value + } + }); + } + else + { + var v = stateEvent.ContentAsJsonNode; + v["url"] = value; + stateEvent.ContentAsJsonNode = v; + } + } + } + + [JsonIgnore] + public string GuestAccess + { + get + { + var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.guest_access"); + if (stateEvent == null) + { + InitialState.Add(new StateEvent() + { + Type = "m.room.guest_access", + Content = new JsonObject() + { + ["guest_access"] = "can_join" + } + }); + return "can_join"; + } + + return stateEvent.ContentAsJsonNode["guest_access"].GetValue<string>(); + } + set + { + var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.guest_access"); + if (stateEvent == null) + { + InitialState.Add(new StateEvent() + { + Type = "m.room.guest_access", + Content = new JsonObject() + { + ["guest_access"] = value + } + }); + } + else + { + var v = stateEvent.ContentAsJsonNode; + v["guest_access"] = value; + stateEvent.ContentAsJsonNode = v; + } + } + } + + + [JsonIgnore] public CreationContentBaseType _creationContentBaseType; + + public CreateRoomRequest() => _creationContentBaseType = new(this); + + + public Dictionary<string, string> Validate() + { + Dictionary<string, string> errors = new(); + if (!Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$")) + errors.Add("room_alias_name", "Room alias name must only contain letters, numbers, underscores, and hyphens."); + + return errors; + } +} + +public class CreationContentBaseType +{ + private readonly CreateRoomRequest createRoomRequest; + + public CreationContentBaseType(CreateRoomRequest createRoomRequest) + { + this.createRoomRequest = createRoomRequest; + } + + [JsonPropertyName("type")] + public string Type + { + get => (string)createRoomRequest.CreationContent["type"]; + set + { + if (value is "null" or "") createRoomRequest.CreationContent.Remove("type"); + else createRoomRequest.CreationContent["type"] = value; + } + } +} + +public class PowerLevelEvent +{ + [JsonPropertyName("ban")] public int Ban { get; set; } // = 50; + [JsonPropertyName("events_default")] public int EventsDefault { get; set; } // = 0; + [JsonPropertyName("events")] public Dictionary<string, int> Events { get; set; } // = null!; + [JsonPropertyName("invite")] public int Invite { get; set; } // = 50; + [JsonPropertyName("kick")] public int Kick { get; set; } // = 50; + [JsonPropertyName("notifications")] public NotificationsPL NotificationsPl { get; set; } // = null!; + [JsonPropertyName("redact")] public int Redact { get; set; } // = 50; + [JsonPropertyName("state_default")] public int StateDefault { get; set; } // = 50; + [JsonPropertyName("users")] public Dictionary<string, int> Users { get; set; } // = null!; + [JsonPropertyName("users_default")] public int UsersDefault { get; set; } // = 0; +} + +public class NotificationsPL +{ + [JsonPropertyName("room")] public int Room { get; set; } = 50; +} \ No newline at end of file diff --git a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs new file mode 100644
index 0000000..d86f546 --- /dev/null +++ b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
@@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace MatrixRoomUtils.Core; + +public class StateEventResponse +{ + [JsonPropertyName("Content")] + public dynamic Content { get; set; } + [JsonPropertyName("origin_server_ts")] + public long OriginServerTs { get; set; } + [JsonPropertyName("RoomId")] + public string RoomId { get; set; } + [JsonPropertyName("Sender")] + public string Sender { get; set; } + [JsonPropertyName("StateKey")] + public string StateKey { get; set; } + [JsonPropertyName("Type")] + public string Type { get; set; } + [JsonPropertyName("Unsigned")] + public dynamic Unsigned { get; set; } + [JsonPropertyName("EventId")] + public string EventId { get; set; } + [JsonPropertyName("UserId")] + public string UserId { get; set; } + [JsonPropertyName("ReplacesState")] + public string ReplacesState { get; set; } + [JsonPropertyName("PrevContent")] + public dynamic PrevContent { get; set; } +} + +public class StateEventResponse<T> : StateEventResponse where T : class +{ + public T content { get; set; } +} \ No newline at end of file diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
index 362abf4..b96546e 100644 --- a/MatrixRoomUtils.Core/Room.cs +++ b/MatrixRoomUtils.Core/Room.cs
@@ -40,14 +40,6 @@ public class Room } var cache = RuntimeCache.GenericResponseCache[cache_key]; - cache.DefaultExpiry = type switch - { - "m.room.name" => TimeSpan.FromMinutes(30), - "org.matrix.mjolnir.shortcode" => TimeSpan.FromHours(4), - "" => TimeSpan.FromSeconds(0), - _ => TimeSpan.FromMinutes(15) - }; - if (cache.ContainsKey(stateCombo)) { if (cache[stateCombo].ExpiryTime > DateTime.Now) @@ -76,14 +68,28 @@ public class Room } var result = await res.Content.ReadFromJsonAsync<JsonElement>(); - - cache[stateCombo] = new GenericResult<object>() + var expiryTime = type switch { - Result = result + "m.room.name" => TimeSpan.FromMinutes(30), + "org.matrix.mjolnir.shortcode" => TimeSpan.FromHours(4), + "" => TimeSpan.FromSeconds(0), + _ => TimeSpan.FromMinutes(15) }; + if(!string.IsNullOrWhiteSpace(type) && !string.IsNullOrWhiteSpace(state_key)) + cache[stateCombo] = new GenericResult<object>() + { + Result = result, + ExpiryTime = DateTime.Now.Add(expiryTime) + }; _semaphore.Release(); return result; } + public async Task<T?> GetStateAsync<T>(string type, string state_key = "", bool logOnFailure = false) + { + var res = await GetStateAsync(type, state_key, logOnFailure); + if (res == null) return default; + return res.Value.Deserialize<T>(); + } public async Task<string> GetNameAsync() { @@ -115,8 +121,8 @@ public class Room 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(); + if(member.GetProperty("Type").GetString() != "m.room.member") continue; + var member_id = member.GetProperty("StateKey").GetString(); members.Add(member_id); } @@ -196,7 +202,7 @@ public class CreateEvent [JsonPropertyName("room_version")] public string RoomVersion { get; set; } [JsonPropertyName("type")] - public string Type { get; set; } + public string? Type { get; set; } [JsonPropertyName("predecessor")] public object? Predecessor { get; set; } diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
index 4e756a7..380a150 100644 --- a/MatrixRoomUtils.Core/RuntimeCache.cs +++ b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -23,6 +23,21 @@ public class RuntimeCache { Console.WriteLine($"RuntimeCache.SaveObject({key}, {value}) was called, but no callback was set!"); }; + + static RuntimeCache() + { + Task.Run(async () => + { + while (true) + { + await Task.Delay(1000); + foreach (var (key, value) in RuntimeCache.GenericResponseCache) + { + SaveObject("rory.matrixroomutils.generic_cache:" + key, value); + } + } + }); + } } @@ -41,7 +56,6 @@ 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, 0, 0); public string Name { get; set; } = null!; public GenericResult<T> this[string key] { @@ -49,19 +63,12 @@ public class ObjectCache<T> where T : class { if (Cache.ContainsKey(key)) { - Console.WriteLine($"cache.get({key}): hit"); + // 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 - { - Cache.Remove(key); - } - catch (Exception e) - { - Console.WriteLine($"Failed to remove {key} from cache: {e.Message}"); - } + if(Cache[key].ExpiryTime < DateTime.Now) + Console.WriteLine($"WARNING: item {key} in cache {Name} expired at {Cache[key].ExpiryTime}:\n{Cache[key].Result.ToJson(indent: false)}"); + return Cache[key]; + } Console.WriteLine($"cache.get({key}): miss"); return null; @@ -69,7 +76,6 @@ public class ObjectCache<T> where T : class 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)}"); @@ -90,7 +96,7 @@ 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); + //RuntimeCache.SaveObject("rory.matrixroomutils.generic_cache:" + Name, this); } }); } diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index df7267d..2201587 100644 --- a/MatrixRoomUtils.Core/StateEvent.cs +++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -1,53 +1,38 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + namespace MatrixRoomUtils.Core; public class StateEvent { - //example: - /* - { - "content": { - "avatar_url": "mxc://matrix.org/BnmEjNvGAkStmAoUiJtEbycT", - "displayname": "X ⊂ Shekhinah | she/her | you", - "membership": "join" - }, - "origin_server_ts": 1682668449785, - "room_id": "!wDPwzxYCNPTkHGHCFT:the-apothecary.club", - "sender": "@kokern:matrix.org", - "state_key": "@kokern:matrix.org", - "type": "m.room.member", - "unsigned": { - "replaces_state": "$7BWfzN15LN8FFUing1hiUQWFfxnOusrEHYFNiOnNrlM", - "prev_content": { - "avatar_url": "mxc://matrix.org/hEQbGywixsjpxDrWvUYEFNur", - "displayname": "X ⊂ Shekhinah | she/her | you", - "membership": "join" - }, - "prev_sender": "@kokern:matrix.org" - }, - "event_id": "$6AGoMCaxqcOeIIDbez1f0VKwLkOEq3EiVLdlsoxDpNg", - "user_id": "@kokern:matrix.org", - "replaces_state": "$7BWfzN15LN8FFUing1hiUQWFfxnOusrEHYFNiOnNrlM", - "prev_content": { - "avatar_url": "mxc://matrix.org/hEQbGywixsjpxDrWvUYEFNur", - "displayname": "X ⊂ Shekhinah | she/her | you", - "membership": "join" + [JsonPropertyName("content")] + public dynamic Content { get; set; } = new{}; + [JsonPropertyName("state_key")] + public string? StateKey { get; set; } + [JsonPropertyName("type")] + public string Type { get; set; } + [JsonPropertyName("replaces_state")] + public string? ReplacesState { get; set; } + + //extra properties + [JsonIgnore] + public JsonNode ContentAsJsonNode + { + get => JsonSerializer.SerializeToNode(Content); + set => Content = value; } - } - */ - public dynamic content { get; set; } - public long origin_server_ts { get; set; } - public string room_id { get; set; } - public string sender { get; set; } - public string state_key { get; set; } - public string type { get; set; } - public dynamic unsigned { get; set; } - public string event_id { get; set; } - public string user_id { get; set; } - public string replaces_state { get; set; } - public dynamic prev_content { get; set; } } public class StateEvent<T> : StateEvent where T : class { - public T content { get; set; } + public new T content { get; set; } + + + [JsonIgnore] + public new JsonNode ContentAsJsonNode + { + get => JsonSerializer.SerializeToNode(Content); + set => Content = value.Deserialize<T>(); + } } \ No newline at end of file