about summary refs log tree commit diff
path: root/LibMatrix/Responses
diff options
context:
space:
mode:
Diffstat (limited to 'LibMatrix/Responses')
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs36
-rw-r--r--LibMatrix/Responses/DeviceKeysUploadRequest.cs24
-rw-r--r--LibMatrix/Responses/EventIdResponse.cs8
-rw-r--r--LibMatrix/Responses/Federation/ServerKeysResponse.cs55
-rw-r--r--LibMatrix/Responses/Federation/ServerVersionResponse.cs16
-rw-r--r--LibMatrix/Responses/Federation/SignedObject.cs69
-rw-r--r--LibMatrix/Responses/LoginResponse.cs20
-rw-r--r--LibMatrix/Responses/MessagesResponse.cs17
-rw-r--r--LibMatrix/Responses/SyncResponse.cs66
-rw-r--r--LibMatrix/Responses/UserDirectoryResponse.cs30
-rw-r--r--LibMatrix/Responses/UserIdAndReason.cs11
-rw-r--r--LibMatrix/Responses/UserProfileResponse.cs15
-rw-r--r--LibMatrix/Responses/WhoAmIResponse.cs14
13 files changed, 344 insertions, 37 deletions
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs

index 6f47183..b4dcc78 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using LibMatrix.EventTypes; -using LibMatrix.EventTypes.Spec.State; +using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; namespace LibMatrix.Responses; @@ -26,39 +26,43 @@ public class CreateRoomRequest { //we dont want to use this, we want more control // [JsonPropertyName("preset")] - // public string Preset { get; set; } = null!; + // public string Preset { get; set; } [JsonPropertyName("initial_state")] - public List<StateEvent>? InitialState { get; set; } + public List<MatrixEvent>? InitialState { get; set; } /// <summary> /// One of: ["public", "private"] /// </summary> [JsonPropertyName("visibility")] + // ReSharper disable once UnusedAutoPropertyAccessor.Global public string? Visibility { get; set; } [JsonPropertyName("power_level_content_override")] - public RoomPowerLevelEventContent? PowerLevelContentOverride { get; set; } = null!; + public RoomPowerLevelEventContent? PowerLevelContentOverride { get; set; } [JsonPropertyName("creation_content")] - public JsonObject CreationContent { get; set; } = new(); + public Dictionary<string, object> CreationContent { get; set; } = new(); [JsonPropertyName("invite")] public List<string>? Invite { get; set; } + [JsonPropertyName("room_version")] + public string? RoomVersion { get; set; } + /// <summary> /// For use only when you can't use the CreationContent property /// </summary> - public StateEvent this[string eventType, string eventKey = ""] { + public MatrixEvent? this[string eventType, string eventKey = ""] { get { - var stateEvent = InitialState.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey); + var stateEvent = InitialState?.FirstOrDefault(x => x.Type == eventType && x.StateKey == eventKey); if (stateEvent == null) - InitialState.Add(stateEvent = new StateEvent { + InitialState?.Add(stateEvent = new MatrixEvent { Type = eventType, StateKey = eventKey, TypedContent = (EventContent)Activator.CreateInstance( - StateEvent.KnownStateEventTypes.FirstOrDefault(x => + MatrixEvent.KnownEventTypes.FirstOrDefault(x => x.GetCustomAttributes<MatrixEventAttribute>()? .Any(y => y.EventName == eventType) ?? false) ?? typeof(UnknownEventContent) )! @@ -77,7 +81,7 @@ public class CreateRoomRequest { public Dictionary<string, string> Validate() { Dictionary<string, string> errors = new(); - if (!Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$")) + if (!string.IsNullOrWhiteSpace(RoomAliasName) && !Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$")) errors.Add("room_alias_name", "Room alias name must only contain letters, numbers, underscores, and hyphens."); @@ -88,7 +92,7 @@ public class CreateRoomRequest { var request = new CreateRoomRequest { Name = name ?? "New public Room", Visibility = "public", - CreationContent = new JsonObject(), + CreationContent = new(), PowerLevelContentOverride = new RoomPowerLevelEventContent { EventsDefault = 0, UsersDefault = 0, @@ -97,7 +101,7 @@ public class CreateRoomRequest { Invite = 25, StateDefault = 10, Redact = 50, - NotificationsPl = new RoomPowerLevelEventContent.NotificationsPL { + NotificationsPl = new RoomPowerLevelEventContent.NotificationsPowerLevels { Room = 10 }, Events = new Dictionary<string, long> { @@ -118,7 +122,7 @@ public class CreateRoomRequest { } }, RoomAliasName = roomAliasName, - InitialState = new List<StateEvent>() + InitialState = new List<MatrixEvent>() }; return request; @@ -128,7 +132,7 @@ public class CreateRoomRequest { var request = new CreateRoomRequest { Name = name ?? "New private Room", Visibility = "private", - CreationContent = new JsonObject(), + CreationContent = new(), PowerLevelContentOverride = new RoomPowerLevelEventContent { EventsDefault = 0, UsersDefault = 0, @@ -137,7 +141,7 @@ public class CreateRoomRequest { Invite = 25, StateDefault = 10, Redact = 50, - NotificationsPl = new RoomPowerLevelEventContent.NotificationsPL { + NotificationsPl = new RoomPowerLevelEventContent.NotificationsPowerLevels { Room = 10 }, Events = new Dictionary<string, long> { @@ -158,7 +162,7 @@ public class CreateRoomRequest { } }, RoomAliasName = roomAliasName, - InitialState = new List<StateEvent>() + InitialState = new List<MatrixEvent>() }; return request; diff --git a/LibMatrix/Responses/DeviceKeysUploadRequest.cs b/LibMatrix/Responses/DeviceKeysUploadRequest.cs new file mode 100644
index 0000000..c93c4c6 --- /dev/null +++ b/LibMatrix/Responses/DeviceKeysUploadRequest.cs
@@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses; + +public class DeviceKeysUploadRequest { + [JsonPropertyName("device_keys")] + public DeviceKeysSchema DeviceKeys { get; set; } + + + [JsonPropertyName("one_time_keys")] + public Dictionary<string, OneTimeKey> OneTimeKeys { get; set; } + + public class DeviceKeysSchema { + [JsonPropertyName("algorithms")] + public List<string> Algorithms { get; set; } + } + public class OneTimeKey { + [JsonPropertyName("key")] + public string Key { get; set; } + + [JsonPropertyName("signatures")] + public Dictionary<string, Dictionary<string, string>> Signatures { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/EventIdResponse.cs b/LibMatrix/Responses/EventIdResponse.cs new file mode 100644
index 0000000..9e23210 --- /dev/null +++ b/LibMatrix/Responses/EventIdResponse.cs
@@ -0,0 +1,8 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses; + +public class EventIdResponse { + [JsonPropertyName("event_id")] + public required string EventId { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Responses/Federation/ServerKeysResponse.cs b/LibMatrix/Responses/Federation/ServerKeysResponse.cs new file mode 100644
index 0000000..982907c --- /dev/null +++ b/LibMatrix/Responses/Federation/ServerKeysResponse.cs
@@ -0,0 +1,55 @@ +using System.Diagnostics; +using System.Text.Json.Serialization; +using LibMatrix.Abstractions; + +namespace LibMatrix.Responses.Federation; + +public class ServerKeysResponse { + [JsonPropertyName("server_name")] + public string ServerName { get; set; } + + [JsonPropertyName("valid_until_ts")] + public ulong ValidUntilTs { get; set; } + + [JsonIgnore] + public DateTime ValidUntil { + get => DateTimeOffset.FromUnixTimeMilliseconds((long)ValidUntilTs).DateTime; + set => ValidUntilTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds(); + } + + [JsonPropertyName("verify_keys")] + public Dictionary<string, CurrentVerifyKey> VerifyKeys { get; set; } = new(); + + [JsonIgnore] + public Dictionary<VersionedKeyId, CurrentVerifyKey> VerifyKeysById { + get => VerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value); + set => VerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value); + } + + [JsonPropertyName("old_verify_keys")] + public Dictionary<string, ExpiredVerifyKey> OldVerifyKeys { get; set; } = new(); + + [JsonIgnore] + public Dictionary<VersionedKeyId, ExpiredVerifyKey> OldVerifyKeysById { + get => OldVerifyKeys.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value); + set => OldVerifyKeys = value.ToDictionary(key => (string)key.Key, key => key.Value); + } + + [DebuggerDisplay("{Key}")] + public class CurrentVerifyKey { + [JsonPropertyName("key")] + public required string Key { get; set; } + } + + [DebuggerDisplay("{Key} (expired {Expired})")] + public class ExpiredVerifyKey : CurrentVerifyKey { + [JsonPropertyName("expired_ts")] + public ulong ExpiredTs { get; set; } + + [JsonIgnore] + public DateTime Expired { + get => DateTimeOffset.FromUnixTimeMilliseconds((long)ExpiredTs).DateTime; + set => ExpiredTs = (ulong)new DateTimeOffset(value).ToUnixTimeMilliseconds(); + } + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/Federation/ServerVersionResponse.cs b/LibMatrix/Responses/Federation/ServerVersionResponse.cs new file mode 100644
index 0000000..b09bdd0 --- /dev/null +++ b/LibMatrix/Responses/Federation/ServerVersionResponse.cs
@@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses.Federation; + +public class ServerVersionResponse { + [JsonPropertyName("server")] + public required ServerInfo Server { get; set; } + + public class ServerInfo { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("version")] + public string Version { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/Federation/SignedObject.cs b/LibMatrix/Responses/Federation/SignedObject.cs new file mode 100644
index 0000000..517bb1f --- /dev/null +++ b/LibMatrix/Responses/Federation/SignedObject.cs
@@ -0,0 +1,69 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using ArcaneLibs.Extensions; +using LibMatrix.Abstractions; +using LibMatrix.Homeservers; + +namespace LibMatrix.Responses.Federation; + +[JsonConverter(typeof(SignedObjectConverterFactory))] +public class SignedObject<T> { + [JsonPropertyName("signatures")] + public Dictionary<string, Dictionary<string, string>> Signatures { get; set; } = new(); + + [JsonIgnore] + public Dictionary<string, Dictionary<VersionedKeyId, string>> SignaturesById { + get => Signatures.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (VersionedKeyId)key.Key, key => key.Value)); + set => Signatures = value.ToDictionary(server => server.Key, server => server.Value.ToDictionary(key => (string)key.Key, key => key.Value)); + } + + [JsonExtensionData] + public JsonObject Content { get; set; } = null!; + + [JsonIgnore] + public T TypedContent { + get => Content.Deserialize<T>() ?? throw new JsonException("Failed to deserialize TypedContent from Content."); + set => Content = JsonSerializer.Deserialize<JsonObject>(JsonSerializer.Serialize(value)) ?? new JsonObject(); + } +} + +public class SignedObjectConverter<T> : JsonConverter<SignedObject<T>> { + public override SignedObject<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + var jsonObject = JsonSerializer.Deserialize<JsonObject>(ref reader, options); + if (jsonObject == null) { + throw new JsonException("Failed to deserialize SignedObject, JSON object is null."); + } + + var signatures = jsonObject["signatures"] ?? throw new JsonException("Failed to find 'signatures' property in JSON object."); + jsonObject.Remove("signatures"); + + var signedObject = new SignedObject<T> { + Content = jsonObject, + Signatures = signatures.Deserialize<Dictionary<string, Dictionary<string, string>>>() + ?? throw new JsonException("Failed to deserialize 'signatures' property into Dictionary<string, Dictionary<string, string>>.") + }; + + return signedObject; + } + + public override void Write(Utf8JsonWriter writer, SignedObject<T> value, JsonSerializerOptions options) { + var targetObj = value.Content.DeepClone(); + targetObj["signatures"] = value.Signatures.ToJsonNode(); + JsonSerializer.Serialize(writer, targetObj, options); + } +} + +internal class SignedObjectConverterFactory : JsonConverterFactory { + public override bool CanConvert(Type typeToConvert) { + if (!typeToConvert.IsGenericType) return false; + return typeToConvert.GetGenericTypeDefinition() == typeof(SignedObject<>); + } + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { + var wrappedType = typeToConvert.GetGenericArguments()[0]; + var converter = (JsonConverter)Activator.CreateInstance(typeof(SignedObjectConverter<>).MakeGenericType(wrappedType))!; + return converter; + } +} \ No newline at end of file diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index 28fb245..1944276 100644 --- a/LibMatrix/Responses/LoginResponse.cs +++ b/LibMatrix/Responses/LoginResponse.cs
@@ -1,30 +1,24 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -using LibMatrix.Homeservers; namespace LibMatrix.Responses; public class LoginResponse { [JsonPropertyName("access_token")] - public string AccessToken { get; set; } = null!; + public string AccessToken { get; set; } [JsonPropertyName("device_id")] - public string DeviceId { get; set; } = null!; - - private string? _homeserver; + public string DeviceId { get; set; } [JsonPropertyName("home_server")] + [field: AllowNull, MaybeNull] public string Homeserver { - get => _homeserver ?? UserId.Split(':', 2).Last(); - protected init => _homeserver = value; + get => field ?? UserId.Split(':', 2).Last(); + set; } [JsonPropertyName("user_id")] - public string UserId { get; set; } = null!; - - // public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) { - // var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver); - // await AuthenticatedHomeserverGeneric.Create<AuthenticatedHomeserverGeneric>(Homeserver, AccessToken, proxy); - // } + public string UserId { get; set; } } public class LoginRequest { diff --git a/LibMatrix/Responses/MessagesResponse.cs b/LibMatrix/Responses/MessagesResponse.cs new file mode 100644
index 0000000..1b412fe --- /dev/null +++ b/LibMatrix/Responses/MessagesResponse.cs
@@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses; + +public class MessagesResponse { + [JsonPropertyName("start")] + public string Start { get; set; } + + [JsonPropertyName("end")] + public string? End { get; set; } + + [JsonPropertyName("chunk")] + public List<MatrixEventResponse> Chunk { get; set; } = new(); + + [JsonPropertyName("state")] + public List<MatrixEventResponse> State { get; set; } = new(); +} \ No newline at end of file diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index e4addb6..362ccc4 100644 --- a/LibMatrix/Responses/SyncResponse.cs +++ b/LibMatrix/Responses/SyncResponse.cs
@@ -1,4 +1,6 @@ +using System.Diagnostics; using System.Text.Json.Serialization; +using LibMatrix.EventTypes.Spec.State.RoomInfo; namespace LibMatrix.Responses; @@ -8,16 +10,16 @@ internal partial class SyncResponseSerializerContext : JsonSerializerContext { } public class SyncResponse { [JsonPropertyName("next_batch")] - public string NextBatch { get; set; } = null!; + public string NextBatch { get; set; } [JsonPropertyName("account_data")] public EventList? AccountData { get; set; } [JsonPropertyName("presence")] - public PresenceDataStructure? Presence { get; set; } + public EventList? Presence { get; set; } [JsonPropertyName("device_one_time_keys_count")] - public Dictionary<string, int>? DeviceOneTimeKeysCount { get; set; } = null!; + public Dictionary<string, int>? DeviceOneTimeKeysCount { get; set; } [JsonPropertyName("rooms")] public RoomsDataStructure? Rooms { get; set; } @@ -28,6 +30,9 @@ public class SyncResponse { [JsonPropertyName("device_lists")] public DeviceListsDataStructure? DeviceLists { get; set; } + [JsonPropertyName("gay.rory.libmatrix.msc4222_sync_type")] + public Msc4222SyncType Msc4222Method { get; set; } = Msc4222SyncType.None; + public class DeviceListsDataStructure { [JsonPropertyName("changed")] public List<string>? Changed { get; set; } @@ -39,7 +44,7 @@ public class SyncResponse { // supporting classes public class PresenceDataStructure { [JsonPropertyName("events")] - public List<StateEventResponse> Events { get; set; } = new(); + public List<MatrixEventResponse>? Events { get; set; } } public class RoomsDataStructure { @@ -61,6 +66,22 @@ public class SyncResponse { [JsonPropertyName("state")] public EventList? State { get; set; } + + [JsonPropertyName("state_after")] + public EventList? StateAfter { get; set; } + + [Obsolete("This property is only used for de/serialisation")] + [JsonPropertyName("org.matrix.msc4222.state_after")] + public EventList? StateAfterUnstable { + get => StateAfter; + set => StateAfter = value; + } + + public override string ToString() { + var lastEvent = Timeline?.Events?.LastOrDefault(x => x.Type == "m.room.member"); + var membership = (lastEvent?.TypedContent as RoomMemberEventContent); + return $"LeftRoomDataStructure: {lastEvent?.Sender} {membership?.Membership} ({membership?.Reason})"; + } } public class JoinedRoomDataStructure { @@ -70,6 +91,16 @@ public class SyncResponse { [JsonPropertyName("state")] public EventList? State { get; set; } + [JsonPropertyName("state_after")] + public EventList? StateAfter { get; set; } + + [Obsolete("This property is only used for de/serialisation")] + [JsonPropertyName("org.matrix.msc4222.state_after")] + public EventList? StateAfterUnstable { + get => StateAfter; + set => StateAfter = value; + } + [JsonPropertyName("account_data")] public EventList? AccountData { get; set; } @@ -82,16 +113,16 @@ public class SyncResponse { [JsonPropertyName("summary")] public SummaryDataStructure? Summary { get; set; } - public class TimelineDataStructure { + public class TimelineDataStructure : EventList { public TimelineDataStructure() { } - public TimelineDataStructure(List<StateEventResponse>? events, bool? limited) { + public TimelineDataStructure(List<MatrixEventResponse>? events, bool? limited) { Events = events; Limited = limited; } - [JsonPropertyName("events")] - public List<StateEventResponse>? Events { get; set; } + // [JsonPropertyName("events")] + // public List<MatrixEventResponse>? Events { get; set; } [JsonPropertyName("prev_batch")] public string? PrevBatch { get; set; } @@ -108,6 +139,7 @@ public class SyncResponse { public int HighlightCount { get; set; } } + [DebuggerDisplay("{JoinedMemberCount} joined, {InvitedMemberCount} invited, Heroes: {string.Join(\", \", Heroes ?? [])}")] public class SummaryDataStructure { [JsonPropertyName("m.heroes")] public List<string>? Heroes { get; set; } @@ -125,4 +157,22 @@ public class SyncResponse { public EventList? InviteState { get; set; } } } + + public long GetDerivedSyncTime() { + return ((long[]) [ + AccountData?.Events?.Max(x => x.OriginServerTs) ?? 0, + Presence?.Events?.Max(x => x.OriginServerTs) ?? 0, + ToDevice?.Events?.Max(x => x.OriginServerTs) ?? 0, + Rooms?.Join?.Values.Max(x => x.Timeline?.Events?.Max(y => y.OriginServerTs)) ?? 0, + Rooms?.Invite?.Values.Max(x => x.InviteState?.Events?.Max(y => y.OriginServerTs)) ?? 0, + Rooms?.Leave?.Values.Max(x => x.Timeline?.Events?.Max(y => y.OriginServerTs)) ?? 0 + ]).Max(); + } + + [JsonConverter(typeof(JsonStringEnumConverter<Msc4222SyncType>))] + public enum Msc4222SyncType { + None, + Server, + Emulated + } } \ No newline at end of file diff --git a/LibMatrix/Responses/UserDirectoryResponse.cs b/LibMatrix/Responses/UserDirectoryResponse.cs new file mode 100644
index 0000000..13235d9 --- /dev/null +++ b/LibMatrix/Responses/UserDirectoryResponse.cs
@@ -0,0 +1,30 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses; + +public class UserDirectoryResponse { + [JsonPropertyName("limited")] + public bool Limited { get; set; } + + [JsonPropertyName("results")] + public List<UserDirectoryResult> Results { get; set; } + + public class UserDirectoryResult { + [JsonPropertyName("avatar_url")] + public string? AvatarUrl { get; set; } + + [JsonPropertyName("display_name")] + public string? DisplayName { get; set; } + + [JsonPropertyName("user_id")] + public string UserId { get; set; } + } +} + +public class UserDirectoryRequest { + [JsonPropertyName("search_term")] + public string SearchTerm { get; set; } + + [JsonPropertyName("limit")] + public int? Limit { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Responses/UserIdAndReason.cs b/LibMatrix/Responses/UserIdAndReason.cs new file mode 100644
index 0000000..176cf7c --- /dev/null +++ b/LibMatrix/Responses/UserIdAndReason.cs
@@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses; + +internal class UserIdAndReason(string userId = null!, string reason = null!) { + [JsonPropertyName("user_id")] + public string UserId { get; set; } = userId; + + [JsonPropertyName("reason")] + public string? Reason { get; set; } = reason; +} \ No newline at end of file diff --git a/LibMatrix/Responses/UserProfileResponse.cs b/LibMatrix/Responses/UserProfileResponse.cs
index 6c9380f..30e4c32 100644 --- a/LibMatrix/Responses/UserProfileResponse.cs +++ b/LibMatrix/Responses/UserProfileResponse.cs
@@ -1,3 +1,4 @@ +using System.Text.Json; using System.Text.Json.Serialization; namespace LibMatrix.Responses; @@ -8,4 +9,18 @@ public class UserProfileResponse { [JsonPropertyName("displayname")] public string? DisplayName { get; set; } + + // MSC 4133 - Extending User Profile API with Key:Value pairs + [JsonExtensionData] + public Dictionary<string, JsonElement>? CustomKeys { get; set; } + + public JsonElement? this[string key] { + get => CustomKeys?[key]; + set { + if (value is null) + CustomKeys?.Remove(key); + else + (CustomKeys ??= [])[key] = value.Value; + } + } } \ No newline at end of file diff --git a/LibMatrix/Responses/WhoAmIResponse.cs b/LibMatrix/Responses/WhoAmIResponse.cs new file mode 100644
index 0000000..db47152 --- /dev/null +++ b/LibMatrix/Responses/WhoAmIResponse.cs
@@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses; + +public class WhoAmIResponse { + [JsonPropertyName("user_id")] + public required string UserId { get; set; } + + [JsonPropertyName("device_id")] + public string? DeviceId { get; set; } + + [JsonPropertyName("is_guest")] + public bool? IsGuest { get; set; } +} \ No newline at end of file