From 2fde2d5f961eabf3167280ba55786cdb6b38f2c0 Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 2 May 2025 16:21:13 +0200 Subject: Add support for ignoring users, add user/room/event reporting --- .../Spec/IgnoredUserListEventContent.cs | 27 +++++++++++ .../State/Policy/PolicyRuleStateEventContent.cs | 13 +++++- .../Homeservers/AuthenticatedHomeserverGeneric.cs | 52 +++++++++++++++++++--- 3 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 LibMatrix.EventTypes/Spec/IgnoredUserListEventContent.cs diff --git a/LibMatrix.EventTypes/Spec/IgnoredUserListEventContent.cs b/LibMatrix.EventTypes/Spec/IgnoredUserListEventContent.cs new file mode 100644 index 0000000..3643b8c --- /dev/null +++ b/LibMatrix.EventTypes/Spec/IgnoredUserListEventContent.cs @@ -0,0 +1,27 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace LibMatrix.EventTypes.Spec; + +[MatrixEvent(EventName = EventId)] +public class IgnoredUserListEventContent : EventContent { + public const string EventId = "m.ignored_user_list"; + + [JsonPropertyName("ignored_users")] + public Dictionary IgnoredUsers { get; set; } = new(); + + // Dummy type to provide easy access to the by-spec empty content + public class IgnoredUserContent { + [JsonExtensionData] + public Dictionary? AdditionalData { get; set; } = []; + + public T GetAdditionalData(string key) { + if (AdditionalData == null || !AdditionalData.TryGetValue(key, out var value)) + throw new KeyNotFoundException($"Key '{key}' not found in AdditionalData."); + if (value is T tValue) + return tValue; + throw new InvalidCastException($"Value for key '{key}' cannot be cast to type '{typeof(T)}'. Cannot continue."); + } + } +} \ No newline at end of file diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs index 0cb4a25..6f8c194 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs @@ -97,16 +97,27 @@ public abstract class PolicyRuleEventContent : EventContent { public Regex? GetEntityRegex() => Entity is null ? null : new(Entity.Replace(".", "\\.").Replace("*", ".*").Replace("?", ".")); + public bool IsGlobRule() => + Entity != null + && (Entity.Contains('*') || Entity.Contains('?')); + public bool EntityMatches(string entity) => Entity != null && ( Entity == entity || ( - Entity.Contains("*") || Entity.Contains("?") + IsGlobRule() ? GetEntityRegex()!.IsMatch(entity) : entity == Entity ) ); + + public string? GetNormalizedRecommendation() { + if (Recommendation is "m.ban" or "org.matrix.mjolnir.ban") + return PolicyRecommendationTypes.Ban; + + return Recommendation; + } } public static class PolicyRecommendationTypes { diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index c1bbc5a..55899de 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -5,6 +5,7 @@ using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Web; using ArcaneLibs.Extensions; +using LibMatrix.EventTypes.Spec; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Filters; using LibMatrix.Helpers; @@ -169,8 +170,9 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { try { return await GetAccountDataAsync(key); } - catch (Exception e) { - return default; + catch (MatrixException e) { + if (e is { ErrorCode: MatrixException.ErrorCodes.M_NOT_FOUND }) return default; + throw; } } @@ -188,8 +190,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { public async Task UpdateProfilePropertyAsync(string name, object? value) { var caps = await GetCapabilitiesAsync(); - if(caps is null) throw new Exception("Failed to get capabilities"); - + if (caps is null) throw new Exception("Failed to get capabilities"); } #endregion @@ -532,8 +533,49 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { } #endregion + + public Task ReportRoomAsync(string roomId, string reason) => + ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{roomId}/report", new { + reason + }); + + public async Task ReportRoomEventAsync(string roomId, string eventId, string reason, int score = 0, bool ignoreSender = false) { + await ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{roomId}/report/{eventId}", new { + reason, + score + }); + + if (ignoreSender) { + var eventContent = await GetRoom(roomId).GetEventAsync(eventId); + var sender = eventContent.Sender; + await IgnoreUserAsync(sender); + } + } + + public async Task ReportUserAsync(string userId, string reason, bool ignore = false) { + await ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/users/{userId}/report", new { + reason + }); + + if (ignore) { + await IgnoreUserAsync(userId); + } + } + + public async Task GetIgnoredUserListAsync() { + return await GetAccountDataOrNullAsync(IgnoredUserListEventContent.EventId) ?? new(); + } + + public async Task IgnoreUserAsync(string userId, IgnoredUserListEventContent.IgnoredUserContent? content = null) { + content ??= new(); + + var ignoredUserList = await GetIgnoredUserListAsync(); + ignoredUserList.IgnoredUsers.TryAdd(userId, content); + await SetAccountDataAsync(IgnoredUserListEventContent.EventId, ignoredUserList); + } + private class CapabilitiesResponse { [JsonPropertyName("capabilities")] public Dictionary? Capabilities { get; set; } } -} +} \ No newline at end of file -- cgit 1.5.1