about summary refs log tree commit diff
path: root/LibMatrix
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2024-09-17 14:08:04 +0200
committerRory& <root@rory.gay>2024-09-17 14:08:04 +0200
commit80d0a3cdf6889ee360dad6a2c270fd42249bbf83 (patch)
tree81700b2c4d937d40cca554b136a65d7634fecee0 /LibMatrix
parentArcanelibs changes (diff)
parentFix unit tests, add authenticated media (diff)
downloadLibMatrix-80d0a3cdf6889ee360dad6a2c270fd42249bbf83.tar.xz
Merge branch 'dev/authenticated-media' HEAD master
Diffstat (limited to 'LibMatrix')
-rw-r--r--LibMatrix/Extensions/MatrixHttpClient.Single.cs3
-rw-r--r--LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs107
-rw-r--r--LibMatrix/Homeservers/RemoteHomeServer.cs1
-rw-r--r--LibMatrix/RoomTypes/GenericRoom.cs26
4 files changed, 116 insertions, 21 deletions
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