diff options
author | Rory& <root@rory.gay> | 2024-09-17 14:08:04 +0200 |
---|---|---|
committer | Rory& <root@rory.gay> | 2024-09-17 14:08:04 +0200 |
commit | 80d0a3cdf6889ee360dad6a2c270fd42249bbf83 (patch) | |
tree | 81700b2c4d937d40cca554b136a65d7634fecee0 /LibMatrix | |
parent | Arcanelibs changes (diff) | |
parent | Fix unit tests, add authenticated media (diff) | |
download | LibMatrix-80d0a3cdf6889ee360dad6a2c270fd42249bbf83.tar.xz |
Merge branch 'dev/authenticated-media'
Diffstat (limited to '')
6 files changed, 122 insertions, 25 deletions
diff --git a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs index 6006048..89e2fdb 100644 --- a/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/Policy/PolicyRuleStateEventContent.cs @@ -79,6 +79,7 @@ public abstract class PolicyRuleEventContent : EventContent { /// </summary> [JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")] [FriendlyName(Name = "Expires at")] + [TableHide] public DateTime? ExpiryDateTime { get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime; set { diff --git a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs index 49a1b62..eb156b3 100644 --- a/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs +++ b/LibMatrix.EventTypes/Spec/State/RoomInfo/RoomPowerLevelEventContent.cs @@ -57,12 +57,13 @@ public class RoomPowerLevelEventContent : EventContent { return Users.TryGetValue(userId, out var level) && level >= Events.GetValueOrDefault(eventType, EventsDefault ?? 0); } - public bool UserHasStatePermission(string userId, string eventType) { + public bool UserHasStatePermission(string userId, string eventType, bool log = false) { ArgumentNullException.ThrowIfNull(userId); var userLevel = GetUserPowerLevel(userId); var eventLevel = GetStateEventPowerLevel(eventType); - - Console.WriteLine($"{userId}={userLevel} >= {eventType}={eventLevel} = {userLevel >= eventLevel}"); + + if (log) + Console.WriteLine($"{userId}={userLevel} >= {eventType}={eventLevel} = {userLevel >= eventLevel}"); return userLevel >= eventLevel; } @@ -78,7 +79,7 @@ public class RoomPowerLevelEventContent : EventContent { if (Events is null) return StateDefault ?? 0; return Events.TryGetValue(eventType, out var level) ? level : StateDefault ?? 0; } - + public long GetTimelineEventPowerLevel(string eventType) { ArgumentNullException.ThrowIfNull(eventType); if (Events is null) return EventsDefault ?? 0; diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs index c9cd260..39eb7e5 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs @@ -26,7 +26,8 @@ public class MatrixHttpClient { EnableMultipleHttp2Connections = true }; Client = new HttpClient(handler) { - DefaultRequestVersion = new Version(3, 0) + DefaultRequestVersion = new Version(3, 0), + Timeout = TimeSpan.FromDays(1) }; } catch (PlatformNotSupportedException e) { diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index c729a44..6be49b9 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -406,4 +406,111 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver { public NamedFilterCache FilterCache { get; init; } public NamedFileCache FileCache { get; init; } } + +#region Authenticated Media + + // TODO: implement /_matrix/client/v1/media/config when it's actually useful - https://spec.matrix.org/v1.11/client-server-api/#get_matrixclientv1mediaconfig + + private (string ServerName, string MediaId) ParseMxcUri(string mxcUri) { + if (!mxcUri.StartsWith("mxc://")) throw new ArgumentException("Matrix Content URIs must start with 'mxc://'", nameof(mxcUri)); + var parts = mxcUri[6..].Split('/'); + if (parts.Length != 2) throw new ArgumentException($"Invalid Matrix Content URI '{mxcUri}' passed! Matrix Content URIs must exist of only 2 parts!", nameof(mxcUri)); + return (parts[0], parts[1]); + } + + public async Task<Stream> GetMediaStreamAsync(string mxcUri, string? filename = null, int? timeout = null) { + var (serverName, mediaId) = ParseMxcUri(mxcUri); + try { + var uri = $"/_matrix/client/v1/media/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); + return await res.Content.ReadAsStreamAsync(); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_UNKNOWN" }) throw; + } + + //fallback to legacy media + try { + var uri = $"/_matrix/media/v1/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); + return await res.Content.ReadAsStreamAsync(); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_UNKNOWN" }) throw; + } + + throw new LibMatrixException() { + ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED, + Error = "Failed to download media" + }; + // return default; + } + + public async Task<Stream> GetThumbnailStreamAsync(string mxcUri, int width, int height, string? method = null, int? timeout = null) { + var (serverName, mediaId) = ParseMxcUri(mxcUri); + try { + var uri = new Uri($"/_matrix/client/v1/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()); + + var res = await ClientHttpClient.GetAsync(uri.ToString()); + return await res.Content.ReadAsStreamAsync(); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_UNKNOWN" }) throw; + } + + //fallback to legacy media + try { + var uri = new Uri($"/_matrix/media/v1/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()); + + var res = await ClientHttpClient.GetAsync(uri.ToString()); + return await res.Content.ReadAsStreamAsync(); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_UNKNOWN" }) throw; + } + + throw new LibMatrixException() { + ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED, + Error = "Failed to download media" + }; + // return default; + } + + public async Task<Dictionary<string, JsonValue>?> GetUrlPreviewAsync(string url) { + try { + var res = await ClientHttpClient.GetAsync($"/_matrix/client/v1/media/preview_url?url={HttpUtility.UrlEncode(url)}"); + return await res.Content.ReadFromJsonAsync<Dictionary<string, JsonValue>>(); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_UNRECOGNIZED" }) throw; + } + + //fallback to legacy media + try { + var res = await ClientHttpClient.GetAsync($"/_matrix/media/v1/preview_url?url={HttpUtility.UrlEncode(url)}"); + return await res.Content.ReadFromJsonAsync<Dictionary<string, JsonValue>>(); + } + catch (MatrixException e) { + if (e is not { ErrorCode: "M_UNRECOGNIZED" }) throw; + } + + throw new LibMatrixException() { + ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED, + Error = "Failed to download URL preview" + }; + } + +#endregion } \ No newline at end of file diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index ecf3e3a..f9e3d04 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -107,6 +107,7 @@ public class RemoteHomeserver { #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; diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index b906f08..a1ef617 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -210,10 +210,11 @@ public class GenericRoom { public async Task<RoomIdResponse> JoinAsync(string[]? homeservers = null, string? reason = null, bool checkIfAlreadyMember = true) { if (checkIfAlreadyMember) try { - _ = await GetCreateEventAsync(); - return new RoomIdResponse { - RoomId = RoomId - }; + var ser = await GetStateEventOrNullAsync(RoomMemberEventContent.EventId, Homeserver.UserId); + if (ser?.TypedContent is RoomMemberEventContent { Membership: "join" }) + return new RoomIdResponse { + RoomId = RoomId + }; } catch { } //ignore @@ -316,6 +317,7 @@ public class GenericRoom { public Task<RoomPowerLevelEventContent?> GetPowerLevelsAsync() => GetStateAsync<RoomPowerLevelEventContent>("m.room.power_levels"); + [Obsolete("This method will be merged into GetNameAsync() in the future.")] public async Task<string> GetNameOrFallbackAsync(int maxMemberNames = 2) { try { return await GetNameAsync(); @@ -352,22 +354,6 @@ public class GenericRoom { return Task.WhenAll(tasks); } - public async Task<string?> GetResolvedRoomAvatarUrlAsync(bool useOriginHomeserver = false) { - var avatar = await GetAvatarUrlAsync(); - if (avatar?.Url is null) return null; - if (!avatar.Url.StartsWith("mxc://")) return avatar.Url; - if (useOriginHomeserver) - try { - var hs = avatar.Url.Split('/', 3)[1]; - return await new HomeserverResolverService(NullLogger<HomeserverResolverService>.Instance).ResolveMediaUri(hs, avatar.Url); - } - catch (Exception e) { - Console.WriteLine(e); - } - - return Homeserver.ResolveMediaUri(avatar.Url); - } - #endregion #region Simple calls |