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
|