about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-01-23 19:43:55 +0100
committerRory& <root@rory.gay>2025-01-23 19:43:55 +0100
commitcc61a7ae65d427e862e67ed92ec39f449cb23345 (patch)
tree2a3b709d80ad8fe55b62068e09e6e9ddffc87cfc
parentSome schema changse (required properties) (diff)
downloadLibMatrix-cc61a7ae65d427e862e67ed92ec39f449cb23345.tar.xz
The rest of warning cleanup so far.
-rw-r--r--LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs17
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs15
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs8
-rw-r--r--LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs2
-rw-r--r--LibMatrix/Extensions/CanonicalJsonSerializer.cs11
-rw-r--r--LibMatrix/Extensions/JsonElementExtensions.cs1
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Single.cs8
-rw-r--r--LibMatrix/Helpers/SyncHelper.cs17
-rw-r--r--LibMatrix/Helpers/SyncStateResolver.cs43
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs18
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs20
-rw-r--r--LibMatrix/LibMatrixException.cs3
-rw-r--r--LibMatrix/MatrixException.cs5
-rw-r--r--LibMatrix/Responses/CreateRoomRequest.cs15
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs13
-rw-r--r--Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs9
-rw-r--r--Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs2
17 files changed, 111 insertions, 96 deletions
diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs

index 0569477..86950e5 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs
@@ -43,7 +43,7 @@ public abstract class PolicyRuleEventContent : EventContent { [FriendlyName(Name = "Entity")] public string? Entity { get; set; } - private bool init; + // private bool init; /// <summary> /// Reason this user is banned @@ -93,13 +93,18 @@ public abstract class PolicyRuleEventContent : EventContent { public string GetDraupnir2StateKey() => Convert.ToBase64String(SHA256.HashData($"{Entity}{Recommendation}".AsBytes().ToArray())); - public Regex GetEntityRegex() => new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); + public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); public bool EntityMatches(string entity) => - Entity == entity - || (Entity.Contains("*") || Entity.Contains("?") - ? GetEntityRegex().IsMatch(entity) - : entity == Entity); + Entity != null + && ( + Entity == entity + || ( + Entity.Contains("*") || Entity.Contains("?") + ? GetEntityRegex()!.IsMatch(entity) + : entity == Entity + ) + ); } public static class PolicyRecommendationTypes { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
index 039c61f..37b831a 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomCreateEventContent.cs
@@ -1,8 +1,11 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] +[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Deserialization, public API")] +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "Deserialization, public API")] public class RoomCreateEventContent : EventContent { public const string EventId = "m.room.create"; @@ -20,12 +23,12 @@ public class RoomCreateEventContent : EventContent { [JsonPropertyName("type")] public string? Type { get; set; } +} - public class RoomCreatePredecessor { - [JsonPropertyName("room_id")] - public string? RoomId { get; set; } +public class RoomCreatePredecessor { + [JsonPropertyName("room_id")] + public string? RoomId { get; set; } - [JsonPropertyName("event_id")] - public string? EventId { get; set; } - } + [JsonPropertyName("event_id")] + public string? EventId { get; set; } } \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
index ee1e218..c9aa603 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs
@@ -20,7 +20,7 @@ public class RoomPowerLevelEventContent : EventContent { public long? Kick { get; set; } = 50; [JsonPropertyName("notifications")] - public NotificationsPL? NotificationsPl { get; set; } // = null!; + public NotificationsPowerLevels? NotificationsPl { get; set; } // = null!; [JsonPropertyName("redact")] public long? Redact { get; set; } = 50; @@ -42,19 +42,19 @@ public class RoomPowerLevelEventContent : EventContent { [JsonPropertyName("historical")] public long Historical { get; set; } // = 50; - public class NotificationsPL { + public class NotificationsPowerLevels { [JsonPropertyName("room")] public long Room { get; set; } = 50; } public bool IsUserAdmin(string userId) { ArgumentNullException.ThrowIfNull(userId); - return Users.TryGetValue(userId, out var level) && level >= Events.Max(x => x.Value); + return GetUserPowerLevel(userId) >= Events?.Max(x => x.Value); } public bool UserHasTimelinePermission(string userId, string eventType) { ArgumentNullException.ThrowIfNull(userId); - return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault ?? 0); + return GetUserPowerLevel(userId) >= Events?.GetValueOrDefault(eventType, EventsDefault ?? 0); } public bool UserHasStatePermission(string userId, string eventType, bool log = false) { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
index cb958ae..e3fc892 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomServerACLEventContent.cs
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization; namespace LibMatrix.EventTypes.Spec.State.RoomInfo; [MatrixEvent(EventName = EventId)] -public class RoomServerACLEventContent : EventContent { +public class RoomServerAclEventContent : EventContent { public const string EventId = "m.room.server_acl"; [JsonPropertyName("allow")] diff --git a/LibMatrix/Extensions/CanonicalJsonSerializer.cs b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
index c9d38cd..55a4b1a 100644 --- a/LibMatrix/Extensions/CanonicalJsonSerializer.cs +++ b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
@@ -8,7 +8,7 @@ namespace LibMatrix.Extensions; public static class CanonicalJsonSerializer { // TODO: Alphabetise dictionaries - private static JsonSerializerOptions _options => new() { + private static JsonSerializerOptions JsonOptions => new() { WriteIndented = false, Encoder = UnicodeJsonEncoder.Singleton, }; @@ -19,7 +19,7 @@ public static class CanonicalJsonSerializer { .ToFrozenSet(); private static JsonSerializerOptions MergeOptions(JsonSerializerOptions? inputOptions) { - var newOptions = _options; + var newOptions = JsonOptions; if (inputOptions == null) return newOptions; @@ -43,7 +43,7 @@ public static class CanonicalJsonSerializer { public static String Serialize<TValue>(TValue value, JsonSerializerOptions? options = null) { var newOptions = MergeOptions(options); - return System.Text.Json.JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + return JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... .SortProperties()! .CanonicalizeNumbers()! .ToJsonString(newOptions); @@ -53,13 +53,14 @@ public static class CanonicalJsonSerializer { } - public static String Serialize(object value, Type inputType, JsonSerializerOptions? options = null) => JsonSerializer.Serialize(value, inputType, _options); + public static String Serialize(object value, Type inputType, JsonSerializerOptions? options = null) => JsonSerializer.Serialize(value, inputType, JsonOptions); // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options); // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) #endregion - private static partial class JsonExtensions { + // ReSharper disable once UnusedType.Local + private static class JsonExtensions { public static Action<JsonTypeInfo> AlphabetizeProperties(Type type) { return typeInfo => { if (typeInfo.Kind != JsonTypeInfoKind.Object || !type.IsAssignableFrom(typeInfo.Type)) diff --git a/LibMatrix/Extensions/JsonElementExtensions.cs b/LibMatrix/Extensions/JsonElementExtensions.cs
index c4ed743..dfec95b 100644 --- a/LibMatrix/Extensions/JsonElementExtensions.cs +++ b/LibMatrix/Extensions/JsonElementExtensions.cs
@@ -126,6 +126,7 @@ public static class JsonElementExtensions { $"Encountered dictionary {field.Name} with key type {keyType.Name} and value type {valueType.Name}!"); return field.Value.EnumerateObject() + // TODO: use key.Value? .Where(key => !valueType.IsPrimitive && valueType != typeof(string)) .Aggregate(false, (current, key) => current | key.FindExtraJsonPropertyFieldsByValueKind(containerType, valueType) diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
index 895217d..56925fb 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs
@@ -76,7 +76,7 @@ public class MatrixHttpClient { Console.WriteLine($"Sending {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.GetContentLength())})"); if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null"); - if (!request.RequestUri.IsAbsoluteUri) request.RequestUri = new Uri(BaseAddress, request.RequestUri); + if (!request.RequestUri.IsAbsoluteUri) request.RequestUri = new Uri(BaseAddress ?? throw new InvalidOperationException("Relative URI passed, but no BaseAddress is specified!"), request.RequestUri); foreach (var (key, value) in AdditionalQueryParameters) request.RequestUri = request.RequestUri.AddQuery(key, value); foreach (var (key, value) in DefaultRequestHeaders) { if (request.Headers.Contains(key)) continue; @@ -126,7 +126,7 @@ public class MatrixHttpClient { if (!content.StartsWith('{')) throw new InvalidDataException("Encountered invalid data:\n" + content); //we have a matrix error - MatrixException? ex = null; + MatrixException? ex; try { ex = JsonSerializer.Deserialize<MatrixException>(content); } @@ -140,7 +140,7 @@ public class MatrixHttpClient { Debug.Assert(ex != null, nameof(ex) + " != null"); ex.RawContent = content; // Console.WriteLine($"Failed to send request: {ex}"); - if (ex?.RetryAfterMs is null) throw ex!; + if (ex.RetryAfterMs is null) throw ex!; //we have a ratelimit error await Task.Delay(ex.RetryAfterMs.Value, cancellationToken); request.ResetSendStatus(); @@ -179,7 +179,7 @@ public class MatrixHttpClient { } // GetStreamAsync - public new async Task<Stream> GetStreamAsync(string requestUri, CancellationToken cancellationToken = default) { + public async Task<Stream> GetStreamAsync(string requestUri, CancellationToken cancellationToken = default) { var request = new HttpRequestMessage(HttpMethod.Get, requestUri); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var response = await SendAsync(request, cancellationToken); diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs
index aed56a7..05bfb47 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs
@@ -1,12 +1,10 @@ using System.Diagnostics; -using System.Net.Http.Json; using System.Text.Json; using ArcaneLibs.Collections; using ArcaneLibs.Extensions; using LibMatrix.Filters; using LibMatrix.Homeservers; using LibMatrix.Responses; -using LibMatrix.Utilities; using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; @@ -14,8 +12,8 @@ namespace LibMatrix.Helpers; public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null) { private SyncFilter? _filter; private string? _namedFilterName; - private bool _filterIsDirty = false; - private string? _filterId = null; + private bool _filterIsDirty; + private string? _filterId; public string? Since { get; set; } public int Timeout { get; set; } = 30000; @@ -55,7 +53,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg public TimeSpan MinimumDelay { get; set; } = new(0); - private async Task updateFilterAsync() { + private async Task UpdateFilterAsync() { if (!string.IsNullOrWhiteSpace(NamedFilterName)) { _filterId = await homeserver.NamedCaches.FilterCache.GetOrSetValueAsync(NamedFilterName); if (_filterId is null) @@ -79,7 +77,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg } var sw = Stopwatch.StartNew(); - if (_filterIsDirty) await updateFilterAsync(); + if (_filterIsDirty) await UpdateFilterAsync(); var url = $"/_matrix/client/v3/sync?timeout={Timeout}&set_presence={SetPresence}&full_state={(FullState ? "true" : "false")}"; if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}"; @@ -189,7 +187,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg if (syncResponse.Rooms is { Join.Count: > 0 }) foreach (var updatedRoom in syncResponse.Rooms.Join) { if (updatedRoom.Value.Timeline is null) continue; - foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events) { + foreach (var stateEventResponse in updatedRoom.Value.Timeline.Events ?? []) { stateEventResponse.RoomId = updatedRoom.Key; var tasks = TimelineEventHandlers.Select(x => x(stateEventResponse)).ToList(); await Task.WhenAll(tasks); @@ -216,9 +214,4 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg /// Event fired when an account data event is received /// </summary> public List<Func<StateEventResponse, Task>> AccountDataReceivedHandlers { get; } = new(); - - private void Log(string message) { - if (logger is null) Console.WriteLine(message); - else logger.LogInformation(message); - } } \ No newline at end of file diff --git a/LibMatrix/Helpers/SyncStateResolver.cs b/LibMatrix/Helpers/SyncStateResolver.cs
index 72d600d..4633f06 100644 --- a/LibMatrix/Helpers/SyncStateResolver.cs +++ b/LibMatrix/Helpers/SyncStateResolver.cs
@@ -27,14 +27,13 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge // run sync var sync = await _syncHelper.SyncAsync(cancellationToken); if (sync is null) return await ContinueAsync(cancellationToken); - if (MergedState is null) MergedState = sync; - else MergedState = MergeSyncs(MergedState, sync); + MergedState = MergedState is null ? sync : MergeSyncs(MergedState, sync); Since = sync.NextBatch; return (sync, MergedState); } private SyncResponse MergeSyncs(SyncResponse oldState, SyncResponse newState) { - oldState.NextBatch = newState.NextBatch ?? oldState.NextBatch; + oldState.NextBatch = newState.NextBatch; oldState.AccountData ??= new EventList(); oldState.AccountData.Events ??= new List<StateEventResponse>(); @@ -42,8 +41,11 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge oldState.AccountData.Events.MergeStateEventLists(newState.AccountData?.Events ?? new List<StateEventResponse>()); oldState.Presence ??= new SyncResponse.PresenceDataStructure(); - if (newState.Presence?.Events is not null) - oldState.Presence.Events.MergeStateEventLists(newState.Presence?.Events ?? new List<StateEventResponse>()); + if (newState.Presence?.Events is { Count: > 0 }) + if (oldState.Presence.Events is { Count: > 0 }) + oldState.Presence.Events.MergeStateEventLists(newState.Presence.Events); + else + oldState.Presence.Events = newState.Presence?.Events; oldState.DeviceOneTimeKeysCount ??= new Dictionary<string, int>(); if (newState.DeviceOneTimeKeysCount is not null) @@ -60,12 +62,17 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge oldState.ToDevice.Events.MergeStateEventLists(newState.ToDevice?.Events ?? new List<StateEventResponse>()); oldState.DeviceLists ??= new SyncResponse.DeviceListsDataStructure(); - if (newState.DeviceLists?.Changed is not null) - foreach (var s in oldState.DeviceLists.Changed!) + if (newState.DeviceLists?.Changed is not null) { + oldState.DeviceLists.Changed ??= new List<string>(); + foreach (var s in newState.DeviceLists.Changed) oldState.DeviceLists.Changed.Add(s); - if (newState.DeviceLists?.Left is not null) - foreach (var s in oldState.DeviceLists.Left!) + } + + if (newState.DeviceLists?.Left is not null) { + oldState.DeviceLists.Left ??= new List<string>(); + foreach (var s in newState.DeviceLists.Left) oldState.DeviceLists.Left.Add(s); + } return oldState; } @@ -75,18 +82,18 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge private SyncResponse.RoomsDataStructure MergeRoomsDataStructure(SyncResponse.RoomsDataStructure oldState, SyncResponse.RoomsDataStructure newState) { oldState.Join ??= new Dictionary<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>(); foreach (var (key, value) in newState.Join ?? new Dictionary<string, SyncResponse.RoomsDataStructure.JoinedRoomDataStructure>()) - if (!oldState.Join.ContainsKey(key)) oldState.Join[key] = value; - else oldState.Join[key] = MergeJoinedRoomDataStructure(oldState.Join[key], value); + if (!oldState.Join.TryAdd(key, value)) + oldState.Join[key] = MergeJoinedRoomDataStructure(oldState.Join[key], value); oldState.Invite ??= new Dictionary<string, SyncResponse.RoomsDataStructure.InvitedRoomDataStructure>(); foreach (var (key, value) in newState.Invite ?? new Dictionary<string, SyncResponse.RoomsDataStructure.InvitedRoomDataStructure>()) - if (!oldState.Invite.ContainsKey(key)) oldState.Invite[key] = value; - else oldState.Invite[key] = MergeInvitedRoomDataStructure(oldState.Invite[key], value); + if (!oldState.Invite.TryAdd(key, value)) + oldState.Invite[key] = MergeInvitedRoomDataStructure(oldState.Invite[key], value); oldState.Leave ??= new Dictionary<string, SyncResponse.RoomsDataStructure.LeftRoomDataStructure>(); foreach (var (key, value) in newState.Leave ?? new Dictionary<string, SyncResponse.RoomsDataStructure.LeftRoomDataStructure>()) { - if (!oldState.Leave.ContainsKey(key)) oldState.Leave[key] = value; - else oldState.Leave[key] = MergeLeftRoomDataStructure(oldState.Leave[key], value); + if (!oldState.Leave.TryAdd(key, value)) + oldState.Leave[key] = MergeLeftRoomDataStructure(oldState.Leave[key], value); if (oldState.Invite.ContainsKey(key)) oldState.Invite.Remove(key); if (oldState.Join.ContainsKey(key)) oldState.Join.Remove(key); } @@ -157,9 +164,9 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge oldData.UnreadNotifications.NotificationCount = newData.UnreadNotifications?.NotificationCount ?? oldData.UnreadNotifications.NotificationCount; oldData.Summary ??= new SyncResponse.RoomsDataStructure.JoinedRoomDataStructure.SummaryDataStructure { - Heroes = newData.Summary?.Heroes ?? oldData.Summary.Heroes, - JoinedMemberCount = newData.Summary?.JoinedMemberCount ?? oldData.Summary.JoinedMemberCount, - InvitedMemberCount = newData.Summary?.InvitedMemberCount ?? oldData.Summary.InvitedMemberCount + Heroes = newData.Summary?.Heroes ?? oldData.Summary?.Heroes, + JoinedMemberCount = newData.Summary?.JoinedMemberCount ?? oldData.Summary?.JoinedMemberCount, + InvitedMemberCount = newData.Summary?.InvitedMemberCount ?? oldData.Summary?.InvitedMemberCount }; oldData.Summary.Heroes = newData.Summary?.Heroes ?? oldData.Summary.Heroes; oldData.Summary.JoinedMemberCount = newData.Summary?.JoinedMemberCount ?? oldData.Summary.JoinedMemberCount; diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 40d7fb4..f47ab64 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -375,11 +375,11 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { /// <returns>All account data.</returns> /// <exception cref="Exception"></exception> public async Task<EventList?> EnumerateAccountData() { - var syncHelper = new SyncHelper(this); - syncHelper.FilterId = await NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetAccountData); + var syncHelper = new SyncHelper(this) { + FilterId = await NamedCaches.FilterCache.GetOrSetValueAsync(CommonSyncFilters.GetAccountData) + }; var resp = await syncHelper.SyncAsync(); - if (resp is null) throw new Exception("Sync failed"); - return resp.AccountData; + return resp?.AccountData ?? throw new Exception("Sync failed"); } private Dictionary<string, string>? _namedFilterCache; @@ -431,7 +431,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { //fallback to legacy media try { - var uri = $"/_matrix/media/v1/download/{serverName}/{mediaId}"; + var uri = $"/_matrix/media/v3/download/{serverName}/{mediaId}"; if (!string.IsNullOrWhiteSpace(filename)) uri += $"/{HttpUtility.UrlEncode(filename)}"; if (timeout is not null) uri += $"?timeout_ms={timeout}"; var res = await ClientHttpClient.GetAsync(uri); @@ -455,7 +455,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { uri = uri.AddQuery("width", width.ToString()); uri = uri.AddQuery("height", height.ToString()); if (!string.IsNullOrWhiteSpace(method)) uri = uri.AddQuery("method", method); - if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()); + if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()!); var res = await ClientHttpClient.GetAsync(uri.ToString()); return await res.Content.ReadAsStreamAsync(); @@ -466,11 +466,11 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { //fallback to legacy media try { - var uri = new Uri($"/_matrix/media/v1/thumbnail/{serverName}/{mediaId}"); + var uri = new Uri($"/_matrix/media/v3/thumbnail/{serverName}/{mediaId}"); uri = uri.AddQuery("width", width.ToString()); uri = uri.AddQuery("height", height.ToString()); if (!string.IsNullOrWhiteSpace(method)) uri = uri.AddQuery("method", method); - if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()); + if (timeout is not null) uri = uri.AddQuery("timeout_ms", timeout.ToString()!); var res = await ClientHttpClient.GetAsync(uri.ToString()); return await res.Content.ReadAsStreamAsync(); @@ -497,7 +497,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { //fallback to legacy media try { - var res = await ClientHttpClient.GetAsync($"/_matrix/media/v1/preview_url?url={HttpUtility.UrlEncode(url)}"); + var res = await ClientHttpClient.GetAsync($"/_matrix/media/v3/preview_url?url={HttpUtility.UrlEncode(url)}"); return await res.Content.ReadFromJsonAsync<Dictionary<string, JsonValue>>(); } catch (MatrixException e) { diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index ee70dc3..7995f03 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -49,7 +49,7 @@ public class RemoteHomeserver { var resp = await ClientHttpClient.GetAsync($"/_matrix/client/v3/profile/{HttpUtility.UrlEncode(mxid)}"); var data = await resp.Content.ReadFromJsonAsync<UserProfileResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data); - _profileCache[mxid] = data; + _profileCache[mxid] = data ?? throw new InvalidOperationException($"Could not get profile for {mxid}"); return data; } @@ -58,7 +58,7 @@ public class RemoteHomeserver { var resp = await ClientHttpClient.GetAsync($"/_matrix/client/versions"); var data = await resp.Content.ReadFromJsonAsync<ClientVersionsResponse>(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ClientVersions: " + data); - return data; + return data ?? throw new InvalidOperationException("ClientVersionsResponse is null"); } public async Task<AliasResult> ResolveRoomAliasAsync(string alias) { @@ -66,7 +66,7 @@ public class RemoteHomeserver { var data = await resp.Content.ReadFromJsonAsync<AliasResult>(); //var text = await resp.Content.ReadAsStringAsync(); if (!resp.IsSuccessStatusCode) Console.WriteLine("ResolveAlias: " + data.ToJson()); - return data; + return data ?? throw new InvalidOperationException($"Could not resolve alias {alias}"); } #region Authentication @@ -78,12 +78,11 @@ public class RemoteHomeserver { type = "m.id.user", user = username }, - password = password, + password, initial_device_display_name = deviceName }); var data = await resp.Content.ReadFromJsonAsync<LoginResponse>(); - if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data.ToJson()); - return data; + return data ?? throw new InvalidOperationException("LoginResponse is null"); } public async Task<LoginResponse> RegisterAsync(string username, string password, string? deviceName = null) { @@ -99,18 +98,13 @@ public class RemoteHomeserver { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); var data = await resp.Content.ReadFromJsonAsync<LoginResponse>(); - if (!resp.IsSuccessStatusCode) Console.WriteLine("Register: " + data.ToJson()); - return data; + return data ?? throw new InvalidOperationException("LoginResponse is null"); } #endregion [Obsolete("This call uses the deprecated unauthenticated media endpoints, please switch to the relevant AuthenticatedHomeserver methods instead.", true)] - public string? ResolveMediaUri(string? mxcUri) { - if (mxcUri is null) return null; - if (mxcUri.StartsWith("https://")) return mxcUri; - return $"{ClientHttpClient.BaseAddress}/_matrix/media/v3/download/{mxcUri.Replace("mxc://", "")}".Replace("//_matrix", "/_matrix"); - } + public virtual string? ResolveMediaUri(string? mxcUri) => null; public UserInteractiveAuthClient Auth; } diff --git a/LibMatrix/LibMatrixException.cs b/LibMatrix/LibMatrixException.cs
index 5854826..27cfc2a 100644 --- a/LibMatrix/LibMatrixException.cs +++ b/LibMatrix/LibMatrixException.cs
@@ -1,5 +1,7 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; +// ReSharper disable MemberCanBePrivate.Global namespace LibMatrix; @@ -20,6 +22,7 @@ public class LibMatrixException : Exception { _ => $"Unknown error: {GetAsObject().ToJson(ignoreNull: true)}" }}\nError: {Error}"; + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Follows spec naming")] public static class ErrorCodes { public const string M_NOT_FOUND = "M_NOT_FOUND"; public const string M_UNSUPPORTED = "M_UNSUPPORTED"; diff --git a/LibMatrix/MatrixException.cs b/LibMatrix/MatrixException.cs
index eb207da..3a79af8 100644 --- a/LibMatrix/MatrixException.cs +++ b/LibMatrix/MatrixException.cs
@@ -1,5 +1,7 @@ +using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; using ArcaneLibs.Extensions; +// ReSharper disable MemberCanBePrivate.Global namespace LibMatrix; @@ -17,7 +19,7 @@ public class MatrixException : Exception { [JsonPropertyName("retry_after_ms")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public int? RetryAfterMs { get; set; } - + public string RawContent { get; set; } public object GetAsObject() => new { errcode = ErrorCode, error = Error, soft_logout = SoftLogout, retry_after_ms = RetryAfterMs }; @@ -62,6 +64,7 @@ public class MatrixException : Exception { _ => $"Unknown error: {new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson(ignoreNull: true)}" }}"; + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Follows spec naming")] public static class ErrorCodes { public const string M_FORBIDDEN = "M_FORBIDDEN"; public const string M_UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"; diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 38bc970..741bea1 100644 --- a/LibMatrix/Responses/CreateRoomRequest.cs +++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -36,10 +36,11 @@ public class CreateRoomRequest { /// 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(); @@ -51,11 +52,11 @@ public class CreateRoomRequest { /// For use only when you can't use the CreationContent property /// </summary> - public StateEvent this[string eventType, string eventKey = ""] { + public StateEvent? 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 StateEvent { Type = eventType, StateKey = eventKey, TypedContent = (EventContent)Activator.CreateInstance( @@ -78,7 +79,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."); @@ -98,7 +99,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> { @@ -138,7 +139,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> { diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index b8e68f8..14276d7 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -135,7 +135,7 @@ public class GenericRoom { return await GetStateEventAsync(type, stateKey); } catch (MatrixException e) { - if (e.ErrorCode == "M_NOT_FOUND") return default; + if (e.ErrorCode == "M_NOT_FOUND") return null; throw; } } @@ -237,10 +237,11 @@ public class GenericRoom { var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default }); + if (result is null) throw new Exception("Failed to deserialise members response"); // if (sw.ElapsedMilliseconds > 100) // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); - foreach (var resp in result.Chunk) { + foreach (var resp in result.Chunk ?? []) { if (resp?.Type != "m.room.member") continue; if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue; yield return resp; @@ -261,11 +262,12 @@ public class GenericRoom { var result = await JsonSerializer.DeserializeAsync<ChunkedStateEventResponse>(await res.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { TypeInfoResolver = ChunkedStateEventResponseSerializerContext.Default }); + if (result is null) throw new Exception("Failed to deserialise members response"); // if (sw.ElapsedMilliseconds > 100) // Console.WriteLine($"Members call deserialised in {sw.GetElapsedAndRestart()}"); // else sw.Restart(); var members = new List<StateEventResponse>(); - foreach (var resp in result.Chunk) { + foreach (var resp in result.Chunk ?? []) { if (resp?.Type != "m.room.member") continue; if (joinedOnly && resp.RawContent?["membership"]?.GetValue<string>() != "join") continue; members.Add(resp); @@ -428,7 +430,7 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync<T>(); } - + public async Task<T?> GetRoomAccountDataOrNullAsync<T>(string key) { try { return await GetRoomAccountDataAsync<T>(key); @@ -456,7 +458,8 @@ public class GenericRoom { while (true) { try { return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(url, data)).Content.ReadFromJsonAsync<EventIdResponse>())!; - } catch (MatrixException e) { + } + catch (MatrixException e) { if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; throw; } diff --git a/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs b/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs
index 90a95e4..221d1f4 100644 --- a/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs +++ b/Utilities/LibMatrix.DevTestBot/Bot/Interfaces/CommandContext.cs
@@ -4,8 +4,9 @@ using LibMatrix.RoomTypes; namespace LibMatrix.ExampleBot.Bot.Interfaces; public class CommandContext { - public GenericRoom Room { get; set; } - public StateEventResponse MessageEvent { get; set; } - public string CommandName => (MessageEvent.TypedContent as RoomMessageEventContent).Body.Split(' ')[0][1..]; - public string[] Args => (MessageEvent.TypedContent as RoomMessageEventContent).Body.Split(' ')[1..]; + public required GenericRoom Room { get; init; } + public required StateEventResponse MessageEvent { get; init; } + public string CommandName => MessageContent.Body.Split(' ')[0][1..]; + public string[] Args => MessageContent.Body.Split(' ')[1..]; + private RoomMessageEventContent MessageContent => MessageEvent.TypedContent as RoomMessageEventContent ?? throw new Exception("Message content is not a RoomMessageEventContent"); } \ No newline at end of file diff --git a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
index c24e6e9..0571d5a 100644 --- a/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs +++ b/Utilities/LibMatrix.HomeserverEmulator/Controllers/Rooms/RoomsController.cs
@@ -62,7 +62,7 @@ public class RoomsController(ILogger<RoomsController> logger, TokenService token var room = new RoomStore.Room($"!{Guid.NewGuid()}:{tokenService.GenerateServerName(HttpContext)}"); var eventTypesToTransfer = new[] { - RoomServerACLEventContent.EventId, + RoomServerAclEventContent.EventId, RoomEncryptionEventContent.EventId, RoomNameEventContent.EventId, RoomAvatarEventContent.EventId,