diff options
Diffstat (limited to 'MatrixRoomUtils.Core')
38 files changed, 539 insertions, 352 deletions
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 |