From bba7333ee6581a92bbbc7479d72325e704fe7fa6 Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 8 Aug 2024 02:44:16 +0200 Subject: More synapse admin apis --- LibMatrix/Filters/LocalRoomQueryFilter.cs | 27 -------- LibMatrix/Helpers/SyncHelper.cs | 34 +++++++-- LibMatrix/Helpers/SyncStateResolver.cs | 13 ++-- .../Homeservers/AuthenticatedHomeserverSynapse.cs | 1 - .../SynapseAdminLocalEventReportQueryFilter.cs | 27 ++++++++ .../Filters/SynapseAdminLocalRoomQueryFilter.cs | 27 ++++++++ .../Filters/SynapseAdminLocalUserQueryFilter.cs | 27 ++++++++ .../Models/Responses/AdminRoomListResult.cs | 64 +++++++++++++++++ .../Models/Responses/AdminRoomListingResult.cs | 64 ----------------- .../Models/Responses/AdminUserListResult.cs | 58 ++++++++++++++++ .../Responses/SynapseAdminEventReportListResult.cs | 58 ++++++++++++++++ .../Synapse/SynapseAdminApiClient.cs | 80 ++++++++++++++++++++-- 12 files changed, 369 insertions(+), 111 deletions(-) delete mode 100644 LibMatrix/Filters/LocalRoomQueryFilter.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalEventReportQueryFilter.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListResult.cs delete mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminUserListResult.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminEventReportListResult.cs diff --git a/LibMatrix/Filters/LocalRoomQueryFilter.cs b/LibMatrix/Filters/LocalRoomQueryFilter.cs deleted file mode 100644 index b3bd4c0..0000000 --- a/LibMatrix/Filters/LocalRoomQueryFilter.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace LibMatrix.Filters; - -public class LocalRoomQueryFilter { - public string RoomIdContains { get; set; } = ""; - public string NameContains { get; set; } = ""; - public string CanonicalAliasContains { get; set; } = ""; - public string VersionContains { get; set; } = ""; - public string CreatorContains { get; set; } = ""; - public string EncryptionContains { get; set; } = ""; - public string JoinRulesContains { get; set; } = ""; - public string GuestAccessContains { get; set; } = ""; - public string HistoryVisibilityContains { get; set; } = ""; - - public bool Federatable { get; set; } = true; - public bool Public { get; set; } = true; - - public int JoinedMembersGreaterThan { get; set; } - public int JoinedMembersLessThan { get; set; } = int.MaxValue; - - public int JoinedLocalMembersGreaterThan { get; set; } - public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; - public int StateEventsGreaterThan { get; set; } - public int StateEventsLessThan { get; set; } = int.MaxValue; - - public bool CheckFederation { get; set; } - public bool CheckPublic { get; set; } -} \ No newline at end of file diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index 1833bd0..07c3bb0 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -3,12 +3,13 @@ using System.Net.Http.Json; using ArcaneLibs.Extensions; using LibMatrix.Filters; using LibMatrix.Homeservers; +using LibMatrix.Interfaces.Services; using LibMatrix.Responses; using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; -public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null) { +public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logger = null, IStorageProvider? storageProvider = null) { private SyncFilter? _filter; private string? _namedFilterName; private bool _filterIsDirty = false; @@ -51,7 +52,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) @@ -74,8 +75,27 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg throw new ArgumentNullException(nameof(homeserver.ClientHttpClient), "Null passed as homeserver for SyncHelper!"); } + if (storageProvider is null) return await SyncAsyncInternal(cancellationToken); + + var key = Since ?? "init"; + if (await storageProvider.ObjectExistsAsync(key)) { + var cached = await storageProvider.LoadObjectAsync(key); + // We explicitly check that NextBatch doesn't match since to prevent infinite loops... + if (cached is not null && cached.NextBatch != Since) { + logger?.LogInformation("SyncHelper: Using cached sync response for {}", key); + return cached; + } + } + + var sync = await SyncAsyncInternal(cancellationToken); + // Ditto here. + if (sync is not null && sync.NextBatch != Since) await storageProvider.SaveObjectAsync(key, sync); + return sync; + } + + private async Task SyncAsyncInternal(CancellationToken? cancellationToken = null) { 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}"; @@ -86,11 +106,11 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg try { var httpResp = await homeserver.ClientHttpClient.GetAsync(url, cancellationToken ?? CancellationToken.None); if (httpResp is null) throw new NullReferenceException("Failed to send HTTP request"); - logger?.LogInformation("Got sync response: {} bytes, {} elapsed", httpResp.Content.Headers.ContentLength ?? -1, sw.Elapsed); + logger?.LogInformation("Got sync response: {} bytes, {} elapsed", httpResp.GetContentLength(), sw.Elapsed); var deserializeSw = Stopwatch.StartNew(); - var resp = await httpResp.Content.ReadFromJsonAsync(cancellationToken: cancellationToken ?? CancellationToken.None, + var resp = await httpResp.Content.ReadFromJsonAsync(cancellationToken: cancellationToken ?? CancellationToken.None, jsonTypeInfo: SyncResponseSerializerContext.Default.SyncResponse); - logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.Content.Headers.ContentLength ?? -1, deserializeSw.Elapsed, sw.Elapsed); + logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.GetContentLength(), deserializeSw.Elapsed, sw.Elapsed); var timeToWait = MinimumDelay.Subtract(sw.Elapsed); if (timeToWait.TotalMilliseconds > 0) await Task.Delay(timeToWait); @@ -210,7 +230,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg /// Event fired when an account data event is received /// public List> AccountDataReceivedHandlers { get; } = new(); - + private void Log(string message) { if (logger is null) Console.WriteLine(message); else logger.LogInformation(message); diff --git a/LibMatrix/Helpers/SyncStateResolver.cs b/LibMatrix/Helpers/SyncStateResolver.cs index fcb23c2..0daccec 100644 --- a/LibMatrix/Helpers/SyncStateResolver.cs +++ b/LibMatrix/Helpers/SyncStateResolver.cs @@ -17,7 +17,7 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge public SyncResponse? MergedState { get; set; } - private SyncHelper _syncHelper = new(homeserver, logger); + private SyncHelper _syncHelper = new(homeserver, logger, storageProvider); public async Task<(SyncResponse next, SyncResponse merged)> ContinueAsync(CancellationToken? cancellationToken = null) { // copy properties @@ -27,13 +27,14 @@ public class SyncStateResolver(AuthenticatedHomeserverGeneric homeserver, ILogge _syncHelper.Filter = Filter; _syncHelper.FullState = FullState; // run sync or grab from storage if available - var sync = storageProvider != null && await storageProvider.ObjectExistsAsync(Since ?? "init") - ? await storageProvider.LoadObjectAsync(Since ?? "init") - : await _syncHelper.SyncAsync(cancellationToken); + // var sync = storageProvider != null && await storageProvider.ObjectExistsAsync(Since ?? "init") + // ? await storageProvider.LoadObjectAsync(Since ?? "init") + // : await _syncHelper.SyncAsync(cancellationToken); + var sync = await _syncHelper.SyncAsync(cancellationToken); if (sync is null) return await ContinueAsync(cancellationToken); - if (storageProvider != null && !await storageProvider.ObjectExistsAsync(Since ?? "init")) - await storageProvider.SaveObjectAsync(Since ?? "init", sync); + // if (storageProvider != null && !await storageProvider.ObjectExistsAsync(Since ?? "init")) + // await storageProvider.SaveObjectAsync(Since ?? "init", sync); if (MergedState is null) MergedState = sync; else MergedState = MergeSyncs(MergedState, sync); diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs index 83ebf20..9acdd58 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs @@ -1,7 +1,6 @@ using ArcaneLibs.Extensions; using LibMatrix.Filters; using LibMatrix.Homeservers.ImplementationDetails.Synapse; -using LibMatrix.Responses.Admin; using LibMatrix.Services; namespace LibMatrix.Homeservers; diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalEventReportQueryFilter.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalEventReportQueryFilter.cs new file mode 100644 index 0000000..c34ad7c --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalEventReportQueryFilter.cs @@ -0,0 +1,27 @@ +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; + +public class SynapseAdminLocalEventReportQueryFilter { + public string UserIdContains { get; set; } = ""; + public string NameContains { get; set; } = ""; + public string CanonicalAliasContains { get; set; } = ""; + public string VersionContains { get; set; } = ""; + public string CreatorContains { get; set; } = ""; + public string EncryptionContains { get; set; } = ""; + public string JoinRulesContains { get; set; } = ""; + public string GuestAccessContains { get; set; } = ""; + public string HistoryVisibilityContains { get; set; } = ""; + + public bool Federatable { get; set; } = true; + public bool Public { get; set; } = true; + + public int JoinedMembersGreaterThan { get; set; } + public int JoinedMembersLessThan { get; set; } = int.MaxValue; + + public int JoinedLocalMembersGreaterThan { get; set; } + public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; + public int StateEventsGreaterThan { get; set; } + public int StateEventsLessThan { get; set; } = int.MaxValue; + + public bool CheckFederation { get; set; } + public bool CheckPublic { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs new file mode 100644 index 0000000..b8929a0 --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalRoomQueryFilter.cs @@ -0,0 +1,27 @@ +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; + +public class SynapseAdminLocalRoomQueryFilter { + public string RoomIdContains { get; set; } = ""; + public string NameContains { get; set; } = ""; + public string CanonicalAliasContains { get; set; } = ""; + public string VersionContains { get; set; } = ""; + public string CreatorContains { get; set; } = ""; + public string EncryptionContains { get; set; } = ""; + public string JoinRulesContains { get; set; } = ""; + public string GuestAccessContains { get; set; } = ""; + public string HistoryVisibilityContains { get; set; } = ""; + + public bool Federatable { get; set; } = true; + public bool Public { get; set; } = true; + + public int JoinedMembersGreaterThan { get; set; } + public int JoinedMembersLessThan { get; set; } = int.MaxValue; + + public int JoinedLocalMembersGreaterThan { get; set; } + public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; + public int StateEventsGreaterThan { get; set; } + public int StateEventsLessThan { get; set; } = int.MaxValue; + + public bool CheckFederation { get; set; } + public bool CheckPublic { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs new file mode 100644 index 0000000..62b291b --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Filters/SynapseAdminLocalUserQueryFilter.cs @@ -0,0 +1,27 @@ +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; + +public class SynapseAdminLocalUserQueryFilter { + public string UserIdContains { get; set; } = ""; + public string NameContains { get; set; } = ""; + public string CanonicalAliasContains { get; set; } = ""; + public string VersionContains { get; set; } = ""; + public string CreatorContains { get; set; } = ""; + public string EncryptionContains { get; set; } = ""; + public string JoinRulesContains { get; set; } = ""; + public string GuestAccessContains { get; set; } = ""; + public string HistoryVisibilityContains { get; set; } = ""; + + public bool Federatable { get; set; } = true; + public bool Public { get; set; } = true; + + public int JoinedMembersGreaterThan { get; set; } + public int JoinedMembersLessThan { get; set; } = int.MaxValue; + + public int JoinedLocalMembersGreaterThan { get; set; } + public int JoinedLocalMembersLessThan { get; set; } = int.MaxValue; + public int StateEventsGreaterThan { get; set; } + public int StateEventsLessThan { get; set; } = int.MaxValue; + + public bool CheckFederation { get; set; } + public bool CheckPublic { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListResult.cs new file mode 100644 index 0000000..c9d7e52 --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListResult.cs @@ -0,0 +1,64 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; + +public class AdminRoomListResult { + [JsonPropertyName("offset")] + public int Offset { get; set; } + + [JsonPropertyName("total_rooms")] + public int TotalRooms { get; set; } + + [JsonPropertyName("next_batch")] + public int? NextBatch { get; set; } + + [JsonPropertyName("prev_batch")] + public int? PrevBatch { get; set; } + + [JsonPropertyName("rooms")] + public List Rooms { get; set; } = new(); + + public class AdminRoomListResultRoom { + [JsonPropertyName("room_id")] + public required string RoomId { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("canonical_alias")] + public string? CanonicalAlias { get; set; } + + [JsonPropertyName("joined_members")] + public int JoinedMembers { get; set; } + + [JsonPropertyName("joined_local_members")] + public int JoinedLocalMembers { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + + [JsonPropertyName("creator")] + public string? Creator { get; set; } + + [JsonPropertyName("encryption")] + public string? Encryption { get; set; } + + [JsonPropertyName("federatable")] + public bool Federatable { get; set; } + + [JsonPropertyName("public")] + public bool Public { get; set; } + + [JsonPropertyName("join_rules")] + public string? JoinRules { get; set; } + + [JsonPropertyName("guest_access")] + public string? GuestAccess { get; set; } + + [JsonPropertyName("history_visibility")] + public string? HistoryVisibility { get; set; } + + [JsonPropertyName("state_events")] + public int StateEvents { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs deleted file mode 100644 index 7ab96ac..0000000 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Text.Json.Serialization; - -namespace LibMatrix.Responses.Admin; - -public class AdminRoomListingResult { - [JsonPropertyName("offset")] - public int Offset { get; set; } - - [JsonPropertyName("total_rooms")] - public int TotalRooms { get; set; } - - [JsonPropertyName("next_batch")] - public int? NextBatch { get; set; } - - [JsonPropertyName("prev_batch")] - public int? PrevBatch { get; set; } - - [JsonPropertyName("rooms")] - public List Rooms { get; set; } = new(); - - public class AdminRoomListingResultRoom { - [JsonPropertyName("room_id")] - public required string RoomId { get; set; } - - [JsonPropertyName("name")] - public string? Name { get; set; } - - [JsonPropertyName("canonical_alias")] - public string? CanonicalAlias { get; set; } - - [JsonPropertyName("joined_members")] - public int JoinedMembers { get; set; } - - [JsonPropertyName("joined_local_members")] - public int JoinedLocalMembers { get; set; } - - [JsonPropertyName("version")] - public string? Version { get; set; } - - [JsonPropertyName("creator")] - public string? Creator { get; set; } - - [JsonPropertyName("encryption")] - public string? Encryption { get; set; } - - [JsonPropertyName("federatable")] - public bool Federatable { get; set; } - - [JsonPropertyName("public")] - public bool Public { get; set; } - - [JsonPropertyName("join_rules")] - public string? JoinRules { get; set; } - - [JsonPropertyName("guest_access")] - public string? GuestAccess { get; set; } - - [JsonPropertyName("history_visibility")] - public string? HistoryVisibility { get; set; } - - [JsonPropertyName("state_events")] - public int StateEvents { get; set; } - } -} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminUserListResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminUserListResult.cs new file mode 100644 index 0000000..9b0c481 --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminUserListResult.cs @@ -0,0 +1,58 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; + +public class AdminUserListResult { + [JsonPropertyName("offset")] + public int Offset { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } + + [JsonPropertyName("next_token")] + public string? NextToken { get; set; } + + [JsonPropertyName("users")] + public List Users { get; set; } = new(); + + public class AdminUserListResultUser { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("is_guest")] + public bool? IsGuest { get; set; } + + [JsonPropertyName("admin")] + public bool? Admin { get; set; } + + [JsonPropertyName("user_type")] + public string? UserType { get; set; } + + [JsonPropertyName("deactivated")] + public bool Deactivated { get; set; } + + [JsonPropertyName("erased")] + public bool Erased { get; set; } + + [JsonPropertyName("shadow_banned")] + public bool ShadowBanned { get; set; } + + [JsonPropertyName("displayname")] + public string? DisplayName { get; set; } + + [JsonPropertyName("avatar_url")] + public string? AvatarUrl { get; set; } + + [JsonPropertyName("creation_ts")] + public long CreationTs { get; set; } + + [JsonPropertyName("last_seen_ts")] + public long? LastSeenTs { get; set; } + + [JsonPropertyName("locked")] + public bool Locked { get; set; } + + [JsonPropertyName("approved")] + public bool Approved { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminEventReportListResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminEventReportListResult.cs new file mode 100644 index 0000000..030108a --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/SynapseAdminEventReportListResult.cs @@ -0,0 +1,58 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; + +public class SynapseAdminEventReportListResult { + [JsonPropertyName("offset")] + public int Offset { get; set; } + + [JsonPropertyName("total")] + public int Total { get; set; } + + [JsonPropertyName("next_token")] + public string? NextToken { get; set; } + + [JsonPropertyName("event_reports")] + public List Reports { get; set; } = new(); + + public class SynapseAdminEventReportListResultReport { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("is_guest")] + public bool? IsGuest { get; set; } + + [JsonPropertyName("admin")] + public bool? Admin { get; set; } + + [JsonPropertyName("user_type")] + public string? UserType { get; set; } + + [JsonPropertyName("deactivated")] + public bool Deactivated { get; set; } + + [JsonPropertyName("erased")] + public bool Erased { get; set; } + + [JsonPropertyName("shadow_banned")] + public bool ShadowBanned { get; set; } + + [JsonPropertyName("displayname")] + public string? DisplayName { get; set; } + + [JsonPropertyName("avatar_url")] + public string? AvatarUrl { get; set; } + + [JsonPropertyName("creation_ts")] + public long CreationTs { get; set; } + + [JsonPropertyName("last_seen_ts")] + public long? LastSeenTs { get; set; } + + [JsonPropertyName("locked")] + public bool Locked { get; set; } + + [JsonPropertyName("approved")] + public bool Approved { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs index ac94a7a..b3902eb 100644 --- a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs @@ -1,24 +1,32 @@ +using System.Net.Http.Json; +using System.Text.Json.Nodes; using ArcaneLibs.Extensions; using LibMatrix.Filters; -using LibMatrix.Responses.Admin; +using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters; +using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses; +using LibMatrix.Responses; namespace LibMatrix.Homeservers.ImplementationDetails.Synapse; public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedHomeserver) { - public async IAsyncEnumerable SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", - string? searchTerm = null, LocalRoomQueryFilter? localFilter = null) { - AdminRoomListingResult? res = null; + // https://github.com/element-hq/synapse/tree/develop/docs/admin_api + +#region Rooms + + public async IAsyncEnumerable SearchRoomsAsync(int limit = int.MaxValue, int chunkLimit = 250, string orderBy = "name", + string dir = "f", string? searchTerm = null, SynapseAdminLocalRoomQueryFilter? localFilter = null) { + AdminRoomListResult? res = null; var i = 0; int? totalRooms = null; do { - var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, 250)}&dir={dir}&order_by={orderBy}"; + var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, chunkLimit)}&dir={dir}&order_by={orderBy}"; if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}"; if (res?.NextBatch is not null) url += $"&from={res.NextBatch}"; Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---"); - res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync(url); + res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync(url); totalRooms ??= res.TotalRooms; Console.WriteLine(res.ToJson(false)); foreach (var room in res.Rooms) { @@ -104,4 +112,64 @@ public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedH } } while (i < Math.Min(limit, totalRooms ?? limit)); } + +#endregion + +#region Users + + public async IAsyncEnumerable SearchUsersAsync(int limit = int.MaxValue, int chunkLimit = 250, + SynapseAdminLocalUserQueryFilter? localFilter = null) { + // TODO: implement filters + string? from = null; + while (limit > 0) { + var url = new Uri("/_synapse/admin/v3/users", UriKind.Relative); + url = url.AddQuery("limit", Math.Min(limit, chunkLimit).ToString()); + if (!string.IsNullOrWhiteSpace(from)) url = url.AddQuery("from", from); + Console.WriteLine($"--- ADMIN Querying User List with URL: {url} ---"); + // TODO: implement URI methods in http client + var res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync(url.ToString()); + foreach (var user in res.Users) { + limit--; + yield return user; + } + + if (string.IsNullOrWhiteSpace(res.NextToken)) break; + from = res.NextToken; + } + } + + public async Task LoginUserAsync(string userId, TimeSpan expireAfter) { + var url = new Uri($"/_synapse/admin/v1/users/{userId.UrlEncode()}/login", UriKind.Relative); + url.AddQuery("valid_until_ms", DateTimeOffset.UtcNow.Add(expireAfter).ToUnixTimeMilliseconds().ToString()); + var resp = await authenticatedHomeserver.ClientHttpClient.PostAsJsonAsync(url.ToString(), new()); + var loginResp = await resp.Content.ReadFromJsonAsync(); + loginResp.UserId = userId; // Synapse only returns the access token + return loginResp; + } + +#endregion + +#region Reports + + public async IAsyncEnumerable GetEventReportsAsync(int limit = int.MaxValue, int chunkLimit = 250, + string dir = "f", SynapseAdminLocalEventReportQueryFilter? filter = null) { + // TODO: implement filters + string? from = null; + while (limit > 0) { + var url = new Uri("/_synapse/admin/v1/event_reports", UriKind.Relative); + url = url.AddQuery("limit", Math.Min(limit, chunkLimit).ToString()); + if (!string.IsNullOrWhiteSpace(from)) url = url.AddQuery("from", from); + Console.WriteLine($"--- ADMIN Querying Reports with URL: {url} ---"); + var res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync(url.ToString()); + foreach (var report in res.Reports) { + limit--; + yield return report; + } + + if (string.IsNullOrWhiteSpace(res.NextToken)) break; + from = res.NextToken; + } + } + +#endregion } \ No newline at end of file -- cgit 1.4.1