diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
index 74e85b1..23e98ae 100644
--- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -2,6 +2,7 @@ using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Filters;
using MatrixRoomUtils.Core.Helpers;
@@ -17,15 +18,16 @@ public class AuthenticatedHomeServer : IHomeServer {
public readonly HomeserverAdminApi Admin;
public readonly SyncHelper SyncHelper;
- public AuthenticatedHomeServer(string canonicalHomeServerDomain, string accessToken, TieredStorageService storage) {
+ public AuthenticatedHomeServer(TieredStorageService storage, string canonicalHomeServerDomain, string accessToken) {
_storage = storage;
- AccessToken = accessToken;
- HomeServerDomain = canonicalHomeServerDomain;
+ AccessToken = accessToken.Trim();
+ HomeServerDomain = canonicalHomeServerDomain.Trim();
Admin = new HomeserverAdminApi(this);
SyncHelper = new SyncHelper(this, storage);
_httpClient = new MatrixHttpClient();
}
+ public WhoAmIResponse WhoAmI { get; set; } = null!;
public string UserId { get; }
public string AccessToken { get; set; }
@@ -35,6 +37,7 @@ public class AuthenticatedHomeServer : IHomeServer {
_httpClient = new MatrixHttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
Console.WriteLine("[AHS] Finished setting up http client");
+ WhoAmI = (await _httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!;
return this;
}
@@ -54,7 +57,7 @@ public class AuthenticatedHomeServer : IHomeServer {
}
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));
+ var res = await _httpClient.PostAsync($"/_matrix/media/v3/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()}");
@@ -65,7 +68,7 @@ public class AuthenticatedHomeServer : IHomeServer {
}
public async Task<Room> CreateRoom(CreateRoomRequest creationEvent) {
- var res = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/createRoom", creationEvent);
+ var res = await _httpClient.PostAsJsonAsync("/_matrix/client/v3/createRoom", creationEvent);
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
@@ -168,4 +171,14 @@ public class AuthenticatedHomeServer : IHomeServer {
} while (i < Math.Min(limit, totalRooms ?? limit));
}
}
+}
+
+public class WhoAmIResponse {
+ [JsonPropertyName("user_id")]
+ public string UserId { get; set; } = null!;
+
+ [JsonPropertyName("device_id")]
+ public string? DeviceId { get; set; }
+ [JsonPropertyName("is_guest")]
+ public bool? IsGuest { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
index 0f9eb58..83b279a 100644
--- a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
+++ b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
@@ -2,6 +2,7 @@ using System.Net.Http.Json;
using System.Text.Json;
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Responses;
+using MatrixRoomUtils.Core.StateEventTypes;
namespace MatrixRoomUtils.Core.Authentication;
@@ -20,7 +21,7 @@ public class MatrixAuth {
initial_device_display_name = "Rory&::MatrixRoomUtils"
};
Console.WriteLine($"Sending login request to {homeserver}...");
- var resp = await hc.PostAsJsonAsync($"{homeserver}/_matrix/client/r0/login", payload);
+ var resp = await hc.PostAsJsonAsync($"{homeserver}/_matrix/client/v3/login", payload);
Console.WriteLine($"Login: {resp.StatusCode}");
var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data);
diff --git a/MatrixRoomUtils.Core/CreateEvent.cs b/MatrixRoomUtils.Core/CreateEvent.cs
new file mode 100644
index 0000000..a7022c5
--- /dev/null
+++ b/MatrixRoomUtils.Core/CreateEvent.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core;
+
+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; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/EventIdResponse.cs b/MatrixRoomUtils.Core/EventIdResponse.cs
new file mode 100644
index 0000000..77dc7f8
--- /dev/null
+++ b/MatrixRoomUtils.Core/EventIdResponse.cs
@@ -0,0 +1,8 @@
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core;
+
+public class EventIdResponse {
+ [JsonPropertyName("event_id")]
+ public string EventId { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/ClassCollector.cs b/MatrixRoomUtils.Core/Extensions/ClassCollector.cs
new file mode 100644
index 0000000..9d3d3c0
--- /dev/null
+++ b/MatrixRoomUtils.Core/Extensions/ClassCollector.cs
@@ -0,0 +1,30 @@
+using System.Reflection;
+
+namespace MatrixRoomUtils.Core.Extensions;
+
+public class ClassCollector<T> where T : class {
+ static ClassCollector() {
+ if (!typeof(T).IsInterface)
+ throw new ArgumentException(
+ $"ClassCollector<T> must be used with an interface type. Passed type: {typeof(T).Name}");
+ }
+
+ public List<Type> ResolveFromAllAccessibleAssemblies() => AppDomain.CurrentDomain.GetAssemblies().SelectMany(ResolveFromAssembly).ToList();
+
+ public List<Type> ResolveFromObjectReference(object obj) => ResolveFromTypeReference(obj.GetType());
+
+ public List<Type> ResolveFromTypeReference(Type t) => Assembly.GetAssembly(t)?.GetReferencedAssemblies().SelectMany(ResolveFromAssemblyName).ToList() ?? new List<Type>();
+
+ public List<Type> ResolveFromAssemblyName(AssemblyName assemblyName) => ResolveFromAssembly(Assembly.Load(assemblyName));
+
+ public List<Type> ResolveFromAssembly(Assembly assembly) => assembly.GetTypes()
+ .Where(x => x is { IsClass: true, IsAbstract: false } && x.GetInterfaces().Contains(typeof(T))).ToList();
+ // {
+ // List<Type> ret = new();
+ // foreach (var x in assembly.GetTypes().Where(x => x is { IsClass: true, IsAbstract: false } && x.GetInterfaces().Contains(typeof(T))).ToList()) {
+ // // Console.WriteLine($"[!!] Found class {x.FullName}");
+ // ret.Add(x);
+ // }
+ // return ret;
+ // }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/IEnumerableExtensions.cs b/MatrixRoomUtils.Core/Extensions/IEnumerableExtensions.cs
new file mode 100644
index 0000000..98b0aab
--- /dev/null
+++ b/MatrixRoomUtils.Core/Extensions/IEnumerableExtensions.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Text.Json;
+using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.Responses;
+
+namespace MatrixRoomUtils.Core.Extensions;
+
+public static class IEnumerableExtensions {
+ public static List<StateEventResponse> DeserializeMatrixTypes(this List<JsonElement> stateEvents) {
+ return stateEvents.Select(DeserializeMatrixType).ToList();
+ }
+
+ public static StateEventResponse DeserializeMatrixType(this JsonElement stateEvent) {
+ var type = stateEvent.GetProperty("type").GetString();
+ var knownType = StateEvent.KnownStateEventTypes.FirstOrDefault(x => x.GetCustomAttribute<MatrixEventAttribute>()?.EventName == type);
+ if (knownType == null) {
+ Console.WriteLine($"Warning: unknown event type '{type}'!");
+ return new StateEventResponse();
+ }
+
+ var eventInstance = Activator.CreateInstance(typeof(StateEventResponse).MakeGenericType(knownType))!;
+ stateEvent.Deserialize(eventInstance.GetType());
+
+ return (StateEventResponse) eventInstance;
+ }
+
+ public static void Replace(this List<StateEvent> stateEvents, StateEvent old, StateEvent @new) {
+ var index = stateEvents.IndexOf(old);
+ if (index == -1) return;
+ stateEvents[index] = @new;
+ }
+}
+
+public class MatrixEventAttribute : Attribute {
+ public string EventName { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
index b007136..78f4456 100644
--- a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
@@ -1,12 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;
+using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core.Extensions;
public static class JsonElementExtensions {
- public static void FindExtraJsonFields([DisallowNull] this JsonElement? res, Type t) {
+ public static void FindExtraJsonElementFields([DisallowNull] this JsonElement? res, Type t) {
var props = t.GetProperties();
var unknownPropertyFound = false;
foreach (var field in res.Value.EnumerateObject()) {
@@ -17,4 +18,18 @@ public static class JsonElementExtensions {
if (unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
}
+ public static void FindExtraJsonObjectFields([DisallowNull] this JsonObject? res, Type t) {
+ var props = t.GetProperties();
+ var unknownPropertyFound = false;
+ foreach (var field in res) {
+ if (props.Any(x => x.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name == field.Key)) continue;
+ Console.WriteLine($"[!!] Unknown property {field.Key} in {t.Name}!");
+ unknownPropertyFound = true;
+ // foreach (var propertyInfo in props) {
+ // Console.WriteLine($"[!!] Known property {propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name} in {t.Name}!");
+ // }
+ }
+
+ if (unknownPropertyFound) Console.WriteLine(res.ToJson());
+ }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
index 812c81c..a4b0791 100644
--- a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
@@ -1,5 +1,7 @@
using System.Text.Encodings.Web;
using System.Text.Json;
+using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core.Extensions;
diff --git a/MatrixRoomUtils.Core/Helpers/SyncHelper.cs b/MatrixRoomUtils.Core/Helpers/SyncHelper.cs
index edbb646..04c31cd 100644
--- a/MatrixRoomUtils.Core/Helpers/SyncHelper.cs
+++ b/MatrixRoomUtils.Core/Helpers/SyncHelper.cs
@@ -1,5 +1,7 @@
+using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Interfaces;
using MatrixRoomUtils.Core.Responses;
using MatrixRoomUtils.Core.Services;
using MatrixRoomUtils.Core.StateEventTypes;
@@ -15,7 +17,7 @@ public class SyncHelper {
_storageService = storageService;
}
- public async Task<SyncResult?> Sync(string? since = null) {
+ public async Task<SyncResult?> Sync(string? since = null, CancellationToken? cancellationToken = null) {
var outFileName = "sync-" +
(await _storageService.CacheStorageProvider.GetAllKeys()).Count(x => x.StartsWith("sync")) +
".json";
@@ -23,12 +25,66 @@ public class SyncHelper {
if (!string.IsNullOrWhiteSpace(since)) url += $"&since={since}";
else url += "&full_state=true";
Console.WriteLine("Calling: " + url);
- var res = await _homeServer._httpClient.GetFromJsonAsync<SyncResult>(url);
- await _storageService.CacheStorageProvider.SaveObject(outFileName, res);
- return res;
+ try {
+ var res = await _homeServer._httpClient.GetFromJsonAsync<SyncResult>(url,
+ cancellationToken: cancellationToken ?? CancellationToken.None);
+ await _storageService.CacheStorageProvider.SaveObject(outFileName, res);
+ Console.WriteLine($"Wrote file: {outFileName}");
+ return res;
+ }
+ catch (TaskCanceledException) {
+ Console.WriteLine("Sync cancelled!");
+ }
+ catch (Exception e) {
+ Console.WriteLine(e);
+ }
+ return null;
+ }
+
+ [SuppressMessage("ReSharper", "FunctionNeverReturns")]
+ public async Task RunSyncLoop(CancellationToken? cancellationToken = null, bool skipInitialSyncEvents = true) {
+ SyncResult? sync = null;
+ while (cancellationToken is null || !cancellationToken.Value.IsCancellationRequested) {
+ sync = await Sync(sync?.NextBatch, cancellationToken);
+ Console.WriteLine($"Got sync, next batch: {sync?.NextBatch}!");
+ if (sync == null) continue;
+ if (sync.Rooms is { Invite.Count: > 0 }) {
+ foreach (var roomInvite in sync.Rooms.Invite) {
+ Console.WriteLine(roomInvite.Value.GetType().Name);
+ InviteReceived?.Invoke(this, roomInvite);
+ }
+ }
+
+ if (sync.AccountData is { Events: { Count: > 0 } }) {
+ foreach (var accountDataEvent in sync.AccountData.Events) {
+ AccountDataReceived?.Invoke(this, accountDataEvent);
+ }
+ }
+
+ // Things that are skipped on the first sync
+ if (skipInitialSyncEvents) {
+ skipInitialSyncEvents = false;
+ continue;
+ }
+
+ if (sync.Rooms is { Join.Count: > 0 }) {
+ foreach (var updatedRoom in sync.Rooms.Join) {
+ foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events) {
+ stateEventResponse.RoomId = updatedRoom.Key;
+ TimelineEventReceived?.Invoke(this, stateEventResponse);
+ }
+ }
+ }
+ }
}
-
- public event EventHandler<SyncResult>? ;
+
+ /// <summary>
+ /// Event fired when a room invite is received
+ /// </summary>
+ public event EventHandler<KeyValuePair<string, SyncResult.RoomsDataStructure.InvitedRoomDataStructure>>? InviteReceived;
+
+ public event EventHandler<StateEventResponse>? TimelineEventReceived;
+ public event EventHandler<StateEventResponse>? AccountDataReceived;
}
public class SyncResult {
@@ -36,30 +92,30 @@ public class SyncResult {
public string NextBatch { get; set; }
[JsonPropertyName("account_data")]
- public EventList AccountData { get; set; }
+ public EventList? AccountData { get; set; }
[JsonPropertyName("presence")]
- public PresenceDataStructure Presence { get; set; }
+ public PresenceDataStructure? Presence { get; set; }
[JsonPropertyName("device_one_time_keys_count")]
public Dictionary<string, int> DeviceOneTimeKeysCount { get; set; }
[JsonPropertyName("rooms")]
- public RoomsDataStructure Rooms { get; set; }
+ public RoomsDataStructure? Rooms { get; set; }
// supporting classes
public class PresenceDataStructure {
[JsonPropertyName("events")]
- public List<StateEventResponse<PresenceStateEventData>> Events { get; set; }
+ public List<StateEventResponse> Events { get; set; }
}
public class RoomsDataStructure {
[JsonPropertyName("join")]
- public Dictionary<string, JoinedRoomDataStructure> Join { get; set; }
+ public Dictionary<string, JoinedRoomDataStructure>? Join { get; set; }
[JsonPropertyName("invite")]
- public Dictionary<string, InvitedRoomDataStructure> Invite { get; set; }
-
+ public Dictionary<string, InvitedRoomDataStructure>? Invite { get; set; }
+
public class JoinedRoomDataStructure {
[JsonPropertyName("timeline")]
public TimelineDataStructure Timeline { get; set; }
@@ -75,7 +131,7 @@ public class SyncResult {
[JsonPropertyName("unread_notifications")]
public UnreadNotificationsDataStructure UnreadNotifications { get; set; }
-
+
[JsonPropertyName("summary")]
public SummaryDataStructure Summary { get; set; }
@@ -97,12 +153,14 @@ public class SyncResult {
[JsonPropertyName("highlight_count")]
public int HighlightCount { get; set; }
}
-
+
public class SummaryDataStructure {
[JsonPropertyName("m.heroes")]
public List<string> Heroes { get; set; }
+
[JsonPropertyName("m.invited_member_count")]
public int InvitedMemberCount { get; set; }
+
[JsonPropertyName("m.joined_member_count")]
public int JoinedMemberCount { get; set; }
}
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index c5645e6..fcff0f2 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -1,7 +1,7 @@
using System.Net.Http.Json;
using System.Text.Json;
using MatrixRoomUtils.Core.Extensions;
-using MatrixRoomUtils.Core.Responses;
+using MatrixRoomUtils.Core.StateEventTypes;
namespace MatrixRoomUtils.Core.Interfaces;
@@ -91,7 +91,7 @@ public class IHomeServer {
}
_profileCache.Add(mxid, null);
- var resp = await _httpClient.GetAsync($"/_matrix/client/r0/profile/{mxid}");
+ var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}");
var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
var profile = data.Deserialize<ProfileResponse>();
@@ -99,5 +99,5 @@ public class IHomeServer {
return profile;
}
- public string? ResolveMediaUri(string mxc) => mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/r0/download/");
+ public string? ResolveMediaUri(string mxc) => mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/v3/download/");
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/IStateEventType.cs b/MatrixRoomUtils.Core/Interfaces/IStateEventType.cs
new file mode 100644
index 0000000..053f50c
--- /dev/null
+++ b/MatrixRoomUtils.Core/Interfaces/IStateEventType.cs
@@ -0,0 +1,5 @@
+namespace MatrixRoomUtils.Core.Interfaces;
+
+public interface IStateEventType {
+
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/JoinRules.cs b/MatrixRoomUtils.Core/JoinRules.cs
new file mode 100644
index 0000000..7ce56c4
--- /dev/null
+++ b/MatrixRoomUtils.Core/JoinRules.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core;
+
+public class JoinRules {
+ private static string Public = "public";
+ private static string Invite = "invite";
+ private static 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/MatrixException.cs b/MatrixRoomUtils.Core/MatrixException.cs
index 3df70e1..50fae20 100644
--- a/MatrixRoomUtils.Core/MatrixException.cs
+++ b/MatrixRoomUtils.Core/MatrixException.cs
@@ -52,6 +52,6 @@ public class MatrixException : Exception {
"M_EXCLUSIVE" => "The resource being requested is reserved by an application service, or the application service making the request has not created the resource: " + Error,
"M_RESOURCE_LIMIT_EXCEEDED" => "Exceeded resource limit: " + Error,
"M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" => "Cannot leave server notice room: " + Error,
- _ => "Unknown error: " + new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson()
+ _ => "Unknown error: " + new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true)
};
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj b/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj
index 5ca40bd..a043378 100644
--- a/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj
+++ b/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj
@@ -7,6 +7,9 @@
</PropertyGroup>
<ItemGroup>
+ <Reference Include="Microsoft.AspNetCore.Mvc.Core">
+ <HintPath>..\..\..\.cache\NuGetPackages\microsoft.aspnetcore.app.ref\7.0.5\ref\net7.0\Microsoft.AspNetCore.Mvc.Core.dll</HintPath>
+ </Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
<HintPath>..\..\..\.cache\NuGetPackages\microsoft.extensions.dependencyinjection.abstractions\7.0.0\lib\net7.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
diff --git a/MatrixRoomUtils.Core/MessagesResponse.cs b/MatrixRoomUtils.Core/MessagesResponse.cs
new file mode 100644
index 0000000..7a303bc
--- /dev/null
+++ b/MatrixRoomUtils.Core/MessagesResponse.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.Responses;
+
+namespace MatrixRoomUtils.Core;
+
+public class MessagesResponse {
+ [JsonPropertyName("start")]
+ public string Start { get; set; }
+
+ [JsonPropertyName("end")]
+ public string? End { get; set; }
+
+ [JsonPropertyName("chunk")]
+ public List<StateEventResponse> Chunk { get; set; } = new();
+
+ [JsonPropertyName("state")]
+ public List<StateEventResponse> State { get; set; } = new();
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
index da7d569..8719b5a 100644
--- a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
+++ b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
@@ -1,8 +1,8 @@
-using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
-using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.StateEventTypes;
namespace MatrixRoomUtils.Core.Responses;
@@ -20,6 +20,7 @@ public class CreateRoomRequest {
//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!;
@@ -47,237 +48,12 @@ public class CreateRoomRequest {
}
}
- //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;
- // }
- // }
- // }
-
- public ServerACL ServerACLs {
- get {
- var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
- if (stateEvent == null) {
- InitialState.Add(new StateEvent {
- Type = "m.room.server_acl",
- Content = new JsonObject {
- ["allow"] = new JsonArray {
- "*"
- },
- ["deny"] = new JsonArray()
- }
- });
- return new ServerACL {
- Allow = new List<string> {
- "*"
- },
- Deny = new List<string>(),
- AllowIpLiterals = true
- };
- }
-
- return new ServerACL {
- Allow = stateEvent.ContentAsJsonNode["allow"].Deserialize<List<string>>(),
- Deny = stateEvent.ContentAsJsonNode["deny"].Deserialize<List<string>>(),
- AllowIpLiterals = true
- };
- }
- set {
- Console.WriteLine($"Setting server acl to {value.ToJson()}");
- var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
- if (stateEvent == null)
- InitialState.Add(new StateEvent {
- Type = "m.room.server_acl",
- Content = new JsonObject {
- ["allow"] = JsonNode.Parse(JsonSerializer.Serialize(value.Allow)),
- ["deny"] = JsonNode.Parse(JsonSerializer.Serialize(value.Deny))
- ["allow_ip_literals"] = value.AllowIpLiterals
- }
- });
- else {
- var v = stateEvent.ContentAsJsonNode;
- v["allow"] = JsonNode.Parse(JsonSerializer.Serialize(value.Allow));
- v["deny"] = JsonNode.Parse(JsonSerializer.Serialize(value.Deny));
- v["allow_ip_literals"] = value.AllowIpLiterals;
- stateEvent.ContentAsJsonNode = v;
- Console.WriteLine($"v={v.ToJson()}");
- Console.WriteLine($"stateEvent.ContentAsJsonNode={stateEvent.ContentAsJsonNode.ToJson()}");
- }
- }
- }
-
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.");
+ 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;
-}
-
-public class ServerACL {
- [JsonPropertyName("allow")]
- public List<string> Allow { get; set; } // = null!;
-
- [JsonPropertyName("deny")]
- public List<string> Deny { get; set; } // = null!;
-
- [JsonPropertyName("allow_ip_literals")]
- public bool AllowIpLiterals { get; set; } // = false;
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/CreationContentBaseType.cs b/MatrixRoomUtils.Core/Responses/CreationContentBaseType.cs
new file mode 100644
index 0000000..743c552
--- /dev/null
+++ b/MatrixRoomUtils.Core/Responses/CreationContentBaseType.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core.Responses;
+
+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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/LoginResponse.cs b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
index 3259e44..8d0d94f 100644
--- a/MatrixRoomUtils.Core/Responses/LoginResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
@@ -1,6 +1,7 @@
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.StateEventTypes;
namespace MatrixRoomUtils.Core.Responses;
@@ -19,7 +20,7 @@ public class LoginResponse {
public async Task<ProfileResponse> GetProfile() {
var hc = new HttpClient();
- var resp = await hc.GetAsync($"{HomeServer}/_matrix/client/r0/profile/{UserId}");
+ var resp = await hc.GetAsync($"{HomeServer}/_matrix/client/v3/profile/{UserId}");
var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
return data.Deserialize<ProfileResponse>();
diff --git a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
index 7b138e0..6e67887 100644
--- a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Interfaces;
namespace MatrixRoomUtils.Core.Responses;
@@ -26,7 +27,7 @@ public class StateEventResponse : StateEvent {
[JsonPropertyName("prev_content")]
public dynamic PrevContent { get; set; }
-
+
public class UnsignedData {
[JsonPropertyName("age")]
public ulong Age { get; set; }
@@ -40,9 +41,4 @@ public class StateEventResponse : StateEvent {
[JsonPropertyName("transaction_id")]
public string? TransactionId { get; set; }
}
-}
-
-public class StateEventResponse<T> : StateEventResponse where T : class {
- [JsonPropertyName("content")]
- public T Content { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
index 1568746..59c56ed 100644
--- a/MatrixRoomUtils.Core/Room.cs
+++ b/MatrixRoomUtils.Core/Room.cs
@@ -1,10 +1,8 @@
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Web;
using MatrixRoomUtils.Core.Extensions;
-using MatrixRoomUtils.Core.Responses;
using MatrixRoomUtils.Core.RoomTypes;
namespace MatrixRoomUtils.Core;
@@ -43,7 +41,7 @@ public class Room {
}
public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b", string filter = "") {
- var url = $"/_matrix/client/r0/rooms/{RoomId}/messages?from={from}&limit={limit}&dir={dir}";
+ var url = $"/_matrix/client/v3/rooms/{RoomId}/messages?from={from}&limit={limit}&dir={dir}";
if (!string.IsNullOrEmpty(filter)) url += $"&filter={filter}";
var res = await _httpClient.GetAsync(url);
if (!res.IsSuccessStatusCode) {
@@ -67,12 +65,14 @@ public class Room {
return resn;
}
- public async Task JoinAsync(string[]? homeservers = null) {
- var join_url = $"/_matrix/client/r0/join/{HttpUtility.UrlEncode(RoomId)}";
+ public async Task JoinAsync(string[]? homeservers = null, string? reason = null) {
+ var join_url = $"/_matrix/client/v3/join/{HttpUtility.UrlEncode(RoomId)}";
Console.WriteLine($"Calling {join_url} with {homeservers?.Length ?? 0} via's...");
if (homeservers == null || homeservers.Length == 0) homeservers = new[] { RoomId.Split(':')[1] };
var fullJoinUrl = $"{join_url}?server_name=" + string.Join("&server_name=", homeservers);
- var res = await _httpClient.PostAsync(fullJoinUrl, null);
+ var res = await _httpClient.PostAsJsonAsync(fullJoinUrl, new {
+ reason
+ });
}
public async Task<List<string>> GetMembersAsync(bool joinedOnly = true) {
@@ -138,7 +138,7 @@ public class Room {
var res = await GetStateAsync("m.room.create");
if (!res.HasValue) return new CreateEvent();
- res.FindExtraJsonFields(typeof(CreateEvent));
+ res.FindExtraJsonElementFields(typeof(CreateEvent));
return res.Value.Deserialize<CreateEvent>() ?? new CreateEvent();
}
@@ -151,7 +151,7 @@ public class Room {
}
public async Task ForgetAsync() {
- var res = await _httpClient.PostAsync($"/_matrix/client/r0/rooms/{RoomId}/forget", null);
+ var res = await _httpClient.PostAsync($"/_matrix/client/v3/rooms/{RoomId}/forget", null);
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to forget room {RoomId} - got status: {res.StatusCode}");
throw new Exception($"Failed to forget room {RoomId} - got status: {res.StatusCode}");
@@ -159,7 +159,9 @@ public class Room {
}
public async Task LeaveAsync(string? reason = null) {
- var res = await _httpClient.PostAsync($"/_matrix/client/r0/rooms/{RoomId}/leave", string.IsNullOrWhiteSpace(reason) ? null : new StringContent($"{{\"reason\":\"{reason}\"}}", Encoding.UTF8, "application/json"));
+ var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/leave", new {
+ reason
+ });
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to leave room {RoomId} - got status: {res.StatusCode}");
throw new Exception($"Failed to leave room {RoomId} - got status: {res.StatusCode}");
@@ -168,7 +170,7 @@ public class Room {
public async Task KickAsync(string userId, string? reason = null) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/r0/rooms/{RoomId}/kick", new UserIdAndReason() { user_id = userId, reason = reason });
+ var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/kick", new UserIdAndReason() { UserId = userId, Reason = reason });
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to kick {userId} from room {RoomId} - got status: {res.StatusCode}");
throw new Exception($"Failed to kick {userId} from room {RoomId} - got status: {res.StatusCode}");
@@ -176,7 +178,7 @@ public class Room {
}
public async Task BanAsync(string userId, string? reason = null) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/r0/rooms/{RoomId}/ban", new UserIdAndReason() { user_id = userId, reason = reason });
+ var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban", new UserIdAndReason() { UserId = userId, Reason = reason });
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to ban {userId} from room {RoomId} - got status: {res.StatusCode}");
throw new Exception($"Failed to ban {userId} from room {RoomId} - got status: {res.StatusCode}");
@@ -184,61 +186,31 @@ public class Room {
}
public async Task UnbanAsync(string userId) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/r0/rooms/{RoomId}/unban", new UserIdAndReason() { user_id = userId });
+ var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban", new UserIdAndReason() { UserId = userId });
if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to unban {userId} from room {RoomId} - got status: {res.StatusCode}");
throw new Exception($"Failed to unban {userId} from room {RoomId} - got status: {res.StatusCode}");
}
}
+ public async Task<EventIdResponse> SendStateEventAsync(string eventType, object content) {
+ var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content);
+ if (!res.IsSuccessStatusCode) {
+ Console.WriteLine($"Failed to send state event {eventType} to room {RoomId} - got status: {res.StatusCode}");
+ throw new Exception($"Failed to send state event {eventType} to room {RoomId} - got status: {res.StatusCode}");
+ }
+ return await res.Content.ReadFromJsonAsync<EventIdResponse>();
+ }
+
+ public async Task<EventIdResponse> SendMessageEventAsync(string eventType, object content) {
+ var res = await _httpClient.PutAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/"+new Guid(), content);
+ if (!res.IsSuccessStatusCode) {
+ Console.WriteLine($"Failed to send event {eventType} to room {RoomId} - got status: {res.StatusCode}");
+ throw new Exception($"Failed to send event {eventType} to room {RoomId} - got status: {res.StatusCode}");
+ }
+ return await res.Content.ReadFromJsonAsync<EventIdResponse>();
+ }
public readonly SpaceRoom AsSpace;
-}
-
-internal class UserIdAndReason {
- public string user_id { get; set; }
- public string? reason { get; set; }
-}
-public class MessagesResponse {
- [JsonPropertyName("start")]
- public string Start { get; set; }
-
- [JsonPropertyName("end")]
- public string? End { get; set; }
-
- [JsonPropertyName("chunk")]
- public List<StateEventResponse> Chunk { get; set; } = new();
-
- [JsonPropertyName("state")]
- public List<StateEventResponse> State { get; set; } = new();
-}
-
-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/RoomTypes/SpaceRoom.cs b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
index 7f634dc..6eaa73b 100644
--- a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
+++ b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
@@ -1,5 +1,6 @@
using System.Text.Json;
using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core.RoomTypes;
@@ -11,10 +12,10 @@ public class SpaceRoom : Room {
var rooms = new List<Room>();
var state = await GetStateAsync("");
if (state != null) {
- var states = state.Value.Deserialize<StateEventResponse<object>[]>()!;
+ var states = state.Value.Deserialize<StateEventResponse[]>()!;
foreach (var stateEvent in states.Where(x => x.Type == "m.space.child")) {
var roomId = stateEvent.StateKey;
- if(stateEvent.Content.ToJson() != "{}" || includeRemoved)
+ if(stateEvent.TypedContent.ToJson() != "{}" || includeRemoved)
rooms.Add(await RuntimeCache.CurrentHomeServer.GetRoom(roomId));
}
}
diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
index db71ee5..7ab3952 100644
--- a/MatrixRoomUtils.Core/RuntimeCache.cs
+++ b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -1,5 +1,6 @@
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Responses;
+using MatrixRoomUtils.Core.StateEventTypes;
namespace MatrixRoomUtils.Core;
diff --git a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
index 3db4584..0f09a45 100644
--- a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
+++ b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
@@ -1,5 +1,3 @@
-using MatrixRoomUtils.Core.Attributes;
-
namespace MatrixRoomUtils.Core.Services;
public class HomeserverProviderService {
@@ -13,6 +11,6 @@ public class HomeserverProviderService {
}
public async Task<AuthenticatedHomeServer> GetAuthenticatedWithToken(string homeserver, string accessToken) {
- return await new AuthenticatedHomeServer(homeserver, accessToken, _tieredStorageService).Configure();
+ return await new AuthenticatedHomeServer(_tieredStorageService, homeserver, accessToken).Configure();
}
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Services/ServiceInstaller.cs b/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
index 43255d8..1b275c5 100644
--- a/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
+++ b/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
@@ -1,16 +1,27 @@
-using MatrixRoomUtils.Core.Interfaces.Services;
using Microsoft.Extensions.DependencyInjection;
namespace MatrixRoomUtils.Core.Services;
public static class ServiceInstaller {
- public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services) {
+ public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services, RoryLibMatrixConfiguration? config = null) {
+ //Check required services
if (!services.Any(x => x.ServiceType == typeof(TieredStorageService)))
throw new Exception("[MRUCore/DI] No TieredStorageService has been registered!");
+ //Add config
+ if(config != null)
+ services.AddSingleton(config);
+ else {
+ services.AddSingleton(new RoryLibMatrixConfiguration());
+ }
+ //Add services
services.AddScoped<HomeserverProviderService>();
return services;
}
+}
+
+public class RoryLibMatrixConfiguration {
+ public string AppName { get; set; } = "Rory&::LibMatrix";
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index a8c1fac..cb8f0b4 100644
--- a/MatrixRoomUtils.Core/StateEvent.cs
+++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -1,12 +1,20 @@
+using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
namespace MatrixRoomUtils.Core;
public class StateEvent {
- [JsonPropertyName("content")]
- public dynamic Content { get; set; } = new { };
+ public static List<Type> KnownStateEventTypes =
+ new ClassCollector<IStateEventType>().ResolveFromAllAccessibleAssemblies();
+
+ public object TypedContent {
+ get => RawContent.Deserialize(GetType)!;
+ set => RawContent = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value));
+ }
[JsonPropertyName("state_key")]
public string StateKey { get; set; } = "";
@@ -17,35 +25,40 @@ public class StateEvent {
[JsonPropertyName("replaces_state")]
public string? ReplacesState { get; set; }
- //extra properties
+ [JsonPropertyName("content")]
+ public JsonObject? RawContent { get; set; }
+
+ public T1 GetContent<T1>() where T1 : IStateEventType {
+ return RawContent.Deserialize<T1>();
+ }
+
[JsonIgnore]
- public JsonNode ContentAsJsonNode {
- get => JsonSerializer.SerializeToNode(Content);
- set => Content = value;
+ public Type GetType {
+ get {
+ var type = StateEvent.KnownStateEventTypes.FirstOrDefault(x =>
+ x.GetCustomAttribute<MatrixEventAttribute>()?.EventName == Type);
+ if (type == null) {
+ Console.WriteLine($"Warning: unknown event type '{Type}'!");
+ Console.WriteLine(RawContent.ToJson());
+ return typeof(object);
+ }
+
+ RawContent.FindExtraJsonObjectFields(type);
+
+ return type;
+ }
}
+ //debug
public string dtype {
get {
var res = GetType().Name switch {
- "StateEvent`1" => $"StateEvent<{Content.GetType().Name}>",
+ "StateEvent`1" => $"StateEvent",
_ => GetType().Name
};
return res;
}
}
- public StateEvent<T> As<T>() where T : class => (StateEvent<T>)this;
-}
-
-public class StateEvent<T> : StateEvent where T : class {
- public StateEvent() {
- //import base content if not an empty object
- if (base.Content.GetType() == typeof(T)) {
- Console.WriteLine($"StateEvent<{typeof(T)}> created with base content of type {base.Content.GetType()}. Importing base content.");
- Content = base.Content;
- }
- }
-
- [JsonPropertyName("content")]
- public new T Content { get; set; }
+ public string cdtype => TypedContent.GetType().Name;
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/CanonicalAliasEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/CanonicalAliasEventData.cs
new file mode 100644
index 0000000..2f9502e
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/CanonicalAliasEventData.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.canonical_alias")]
+public class CanonicalAliasEventData : IStateEventType {
+ [JsonPropertyName("alias")]
+ public string? Alias { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/GuestAccessData.cs b/MatrixRoomUtils.Core/StateEventTypes/GuestAccessData.cs
new file mode 100644
index 0000000..1727ce9
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/GuestAccessData.cs
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.guest_access")]
+public class GuestAccessData : IStateEventType {
+ [JsonPropertyName("guest_access")]
+ public string GuestAccess { get; set; }
+
+ public bool IsGuestAccessEnabled {
+ get => GuestAccess == "can_join";
+ set => GuestAccess = value ? "can_join" : "forbidden";
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/HistoryVisibilityData.cs b/MatrixRoomUtils.Core/StateEventTypes/HistoryVisibilityData.cs
new file mode 100644
index 0000000..481cc08
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/HistoryVisibilityData.cs
@@ -0,0 +1,9 @@
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.history_visibility")]
+public class HistoryVisibilityData : IStateEventType {
+ public string HistoryVisibility { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/MemberEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/MemberEventData.cs
new file mode 100644
index 0000000..acf7777
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/MemberEventData.cs
@@ -0,0 +1,23 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.member")]
+public class MemberEventData : IStateEventType {
+ [JsonPropertyName("reason")]
+ public string? Reason { get; set; }
+
+ [JsonPropertyName("membership")]
+ public string Membership { get; set; } = null!;
+
+ [JsonPropertyName("displayname")]
+ public string? Displayname { get; set; }
+
+ [JsonPropertyName("is_direct")]
+ public bool? IsDirect { get; set; }
+
+ [JsonPropertyName("avatar_url")]
+ public string? AvatarUrl { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/MessageEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/MessageEventData.cs
new file mode 100644
index 0000000..ad99709
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/MessageEventData.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+[MatrixEvent(EventName = "m.room.message")]
+public class MessageEventData : IStateEventType {
+ [JsonPropertyName("body")]
+ public string Body { get; set; }
+ [JsonPropertyName("msgtype")]
+ public string MessageType { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
index 6f6d082..e67639b 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
@@ -1,8 +1,9 @@
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Interfaces;
namespace MatrixRoomUtils.Core.StateEventTypes;
-public class PolicyRuleStateEventData {
+public class PolicyRuleStateEventData : IStateEventType {
/// <summary>
/// Entity this ban applies to, can use * and ? as globs.
/// </summary>
diff --git a/MatrixRoomUtils.Core/StateEventTypes/PowerLevelEvent.cs b/MatrixRoomUtils.Core/StateEventTypes/PowerLevelEvent.cs
new file mode 100644
index 0000000..a3e44d1
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/PowerLevelEvent.cs
@@ -0,0 +1,43 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.power_levels")]
+public class PowerLevelEvent : IStateEventType {
+ [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/StateEventTypes/PresenceStateEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/PresenceStateEventData.cs
index d835c95..a17b6f9 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/PresenceStateEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/PresenceStateEventData.cs
@@ -1,8 +1,11 @@
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
namespace MatrixRoomUtils.Core.StateEventTypes;
-public class PresenceStateEventData {
+[MatrixEvent(EventName = "m.presence")]
+public class PresenceStateEventData : IStateEventType {
[JsonPropertyName("presence")]
public string Presence { get; set; }
[JsonPropertyName("last_active_ago")]
diff --git a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs b/MatrixRoomUtils.Core/StateEventTypes/ProfileResponse.cs
index db72386..d36ef74 100644
--- a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/ProfileResponse.cs
@@ -1,7 +1,9 @@
using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
-namespace MatrixRoomUtils.Core.Responses;
+namespace MatrixRoomUtils.Core.StateEventTypes;
+[MatrixEvent(EventName = "m.room.member")]
public class ProfileResponse {
[JsonPropertyName("avatar_url")]
public string? AvatarUrl { get; set; } = "";
diff --git a/MatrixRoomUtils.Core/StateEventTypes/RoomAvatarEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/RoomAvatarEventData.cs
new file mode 100644
index 0000000..03ce16b
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/RoomAvatarEventData.cs
@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.avatar")]
+public class RoomAvatarEventData : IStateEventType {
+ [JsonPropertyName("url")]
+ public string? Url { get; set; }
+
+ [JsonPropertyName("info")]
+ public RoomAvatarInfo? Info { get; set; }
+
+ public class RoomAvatarInfo {
+
+ }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/RoomTopicEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/RoomTopicEventData.cs
new file mode 100644
index 0000000..72651c8
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/RoomTopicEventData.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.topic")]
+public class RoomTopicEventData : IStateEventType {
+ [JsonPropertyName("topic")]
+ public string? Topic { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/ServerACLData.cs b/MatrixRoomUtils.Core/StateEventTypes/ServerACLData.cs
new file mode 100644
index 0000000..41bf0a8
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/ServerACLData.cs
@@ -0,0 +1,17 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "m.room.server_acl")]
+public class ServerACLData : IStateEventType {
+ [JsonPropertyName("allow")]
+ public List<string> Allow { get; set; } // = null!;
+
+ [JsonPropertyName("deny")]
+ public List<string> Deny { get; set; } // = null!;
+
+ [JsonPropertyName("allow_ip_literals")]
+ public bool AllowIpLiterals { get; set; } // = false;
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/UserIdAndReason.cs b/MatrixRoomUtils.Core/UserIdAndReason.cs
new file mode 100644
index 0000000..3801077
--- /dev/null
+++ b/MatrixRoomUtils.Core/UserIdAndReason.cs
@@ -0,0 +1,10 @@
+using System.Text.Json.Serialization;
+
+namespace MatrixRoomUtils.Core;
+
+internal class UserIdAndReason {
+ [JsonPropertyName("user_id")]
+ public string UserId { get; set; }
+ [JsonPropertyName("reason")]
+ public string? Reason { get; set; }
+}
\ No newline at end of file
|