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
|