diff options
author | Emma [it/its]@Rory& <root@rory.gay> | 2024-01-31 12:09:28 +0100 |
---|---|---|
committer | Emma [it/its]@Rory& <root@rory.gay> | 2024-01-31 12:09:28 +0100 |
commit | 9f8d0c85c54b4715974994aea52562072d6f1751 (patch) | |
tree | ce5eaf47b02fb82bc99236b926eb9948322745f7 /LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs | |
parent | Get full state event (diff) | |
download | LibMatrix-9f8d0c85c54b4715974994aea52562072d6f1751.tar.xz |
Better sync filter support, named filters, error handling
Diffstat (limited to 'LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs')
-rw-r--r-- | LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs | 124 |
1 files changed, 112 insertions, 12 deletions
diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index 5db9a48..ef6fa68 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -12,19 +12,21 @@ using LibMatrix.Helpers; using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; +using LibMatrix.Utilities; namespace LibMatrix.Homeservers; public class AuthenticatedHomeserverGeneric(string serverName, string accessToken) : RemoteHomeserver(serverName) { public static async Task<T> Create<T>(string serverName, string accessToken, string? proxy = null) where T : AuthenticatedHomeserverGeneric => await Create(typeof(T), serverName, accessToken, proxy) as T ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}"); + public static async Task<AuthenticatedHomeserverGeneric> Create(Type type, string serverName, string accessToken, string? proxy = null) { if (string.IsNullOrWhiteSpace(proxy)) proxy = null; - if(!type.IsAssignableTo(typeof(AuthenticatedHomeserverGeneric))) throw new ArgumentException("Type must be a subclass of AuthenticatedHomeserverGeneric", nameof(type)); + if (!type.IsAssignableTo(typeof(AuthenticatedHomeserverGeneric))) throw new ArgumentException("Type must be a subclass of AuthenticatedHomeserverGeneric", nameof(type)); var instance = Activator.CreateInstance(type, serverName, accessToken) as AuthenticatedHomeserverGeneric ?? throw new InvalidOperationException($"Failed to create instance of {type.Name}"); - + instance.ClientHttpClient = new() { Timeout = TimeSpan.FromMinutes(15), DefaultRequestHeaders = { @@ -44,7 +46,6 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"); - return instance; } @@ -127,7 +128,7 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke } } - #region Utility Functions +#region Utility Functions public virtual async IAsyncEnumerable<GenericRoom> GetJoinedRoomsByType(string type) { var rooms = await GetJoinedRooms(); @@ -145,9 +146,9 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke } } - #endregion +#endregion - #region Account Data +#region Account Data public virtual async Task<T> GetAccountDataAsync<T>(string key) { // var res = await _httpClient.GetAsync($"/_matrix/client/v3/user/{UserId}/account_data/{key}"); @@ -168,7 +169,7 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke } } - #endregion +#endregion public async Task UpdateProfileAsync(UserProfileResponse? newProfile, bool preserveCustomRoomProfile = true) { if (newProfile is null) return; @@ -290,17 +291,116 @@ public class AuthenticatedHomeserverGeneric(string serverName, string accessToke return await res.Content.ReadFromJsonAsync<RoomIdResponse>() ?? throw new Exception("Failed to join room?"); } - #region Room Profile Utility +#region Room Profile Utility private async Task<KeyValuePair<string, RoomMemberEventContent>> GetOwnRoomProfileWithIdAsync(GenericRoom room) { return new KeyValuePair<string, RoomMemberEventContent>(room.RoomId, await room.GetStateAsync<RoomMemberEventContent>("m.room.member", WhoAmI.UserId!)); } - #endregion - +#endregion + public async Task SetImpersonate(string mxid) { - if(ClientHttpClient.AdditionalQueryParameters.TryGetValue("user_id", out var existingMxid) && existingMxid == mxid && WhoAmI.UserId == mxid) return; + if (ClientHttpClient.AdditionalQueryParameters.TryGetValue("user_id", out var existingMxid) && existingMxid == mxid && WhoAmI.UserId == mxid) return; ClientHttpClient.AdditionalQueryParameters["user_id"] = mxid; WhoAmI = await ClientHttpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"); } -} + + public async Task<FilterIdResponse> UploadFilterAsync(SyncFilter filter) { + var resp = await ClientHttpClient.PostAsJsonAsync("/_matrix/client/v3/user/" + UserId + "/filter", filter); + return await resp.Content.ReadFromJsonAsync<FilterIdResponse>() ?? throw new Exception("Failed to upload filter?"); + } + + public async Task<SyncFilter> GetFilterAsync(string filterId) { + if (_filterCache.TryGetValue(filterId, out var filter)) return filter; + var resp = await ClientHttpClient.GetAsync("/_matrix/client/v3/user/" + UserId + "/filter/" + filterId); + return _filterCache[filterId] = await resp.Content.ReadFromJsonAsync<SyncFilter>() ?? throw new Exception("Failed to get filter?"); + } + +#region Named filters + + private async Task<Dictionary<string, string>?> GetNamedFilterListOrNullAsync(bool cached = true) { + if (cached && _namedFilterCache is not null) return _namedFilterCache; + try { + return _namedFilterCache = await GetAccountDataAsync<Dictionary<string, string>>("gay.rory.libmatrix.named_filters"); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_NOT_FOUND" }) throw; + } + + return null; + } + + /// <summary> + /// Utility function to allow avoiding serverside duplication + /// </summary> + /// <param name="filterName">Name of the filter (<i>please</i> properly namespace and possibly version this...)</param> + /// <param name="filter">The filter data</param> + /// <returns>Filter ID response</returns> + /// <exception cref="Exception"></exception> + public async Task<FilterIdResponse> UploadNamedFilterAsync(string filterName, SyncFilter filter) { + var resp = await ClientHttpClient.PostAsJsonAsync("/_matrix/client/v3/user/" + UserId + "/filter", filter); + var idResp = await resp.Content.ReadFromJsonAsync<FilterIdResponse>() ?? throw new Exception("Failed to upload filter?"); + + var filterList = await GetNamedFilterListOrNullAsync() ?? new(); + filterList[filterName] = idResp.FilterId; + await SetAccountDataAsync("gay.rory.libmatrix.named_filters", filterList); + + _namedFilterCache = filterList; + + return idResp; + } + + public async Task<string?> GetNamedFilterIdOrNullAsync(string filterName) { + var filterList = await GetNamedFilterListOrNullAsync() ?? new(); + return filterList.GetValueOrDefault(filterName); //todo: validate that filter exists + } + + public async Task<SyncFilter?> GetNamedFilterOrNullAsync(string filterName) { + var filterId = await GetNamedFilterIdOrNullAsync(filterName); + if (filterId is null) return null; + return await GetFilterAsync(filterId); + } + + public async Task<string?> GetOrUploadNamedFilterIdAsync(string filterName, SyncFilter? filter = null) { + var filterId = await GetNamedFilterIdOrNullAsync(filterName); + if (filterId is not null) return filterId; + if (filter is null && CommonSyncFilters.FilterMap.TryGetValue(filterName, out var commonFilter)) filter = commonFilter; + if (filter is null) throw new ArgumentException($"Filter is null and no common filter was found, filterName={filterName}", nameof(filter)); + var idResp = await UploadNamedFilterAsync(filterName, filter); + return idResp.FilterId; + } + +#endregion + + public class FilterIdResponse { + [JsonPropertyName("filter_id")] + public required string FilterId { get; set; } + } + + public async Task<Dictionary<string, EventList?>> EnumerateAccountDataPerRoom(bool includeGlobal = false) { + var syncHelper = new SyncHelper(this); + syncHelper.FilterId = await GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetAccountDataWithRooms); + var resp = await syncHelper.SyncAsync(); + if(resp is null) throw new Exception("Sync failed"); + var perRoomAccountData = new Dictionary<string, EventList?>(); + + if(includeGlobal) + perRoomAccountData[""] = resp.AccountData; + foreach (var (roomId, room) in resp.Rooms?.Join ?? []) { + perRoomAccountData[roomId] = room.AccountData; + } + + return perRoomAccountData; + } + + public async Task<EventList?> EnumerateAccountData() { + var syncHelper = new SyncHelper(this); + syncHelper.FilterId = await GetOrUploadNamedFilterIdAsync(CommonSyncFilters.GetAccountData); + var resp = await syncHelper.SyncAsync(); + if(resp is null) throw new Exception("Sync failed"); + return resp.AccountData; + } + + private Dictionary<string, string>? _namedFilterCache; + private Dictionary<string, SyncFilter> _filterCache = new(); +} \ No newline at end of file |