diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index f47ab64..8c95bc3 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -39,11 +39,12 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
public string UserId => WhoAmI.UserId;
public string UserLocalpart => UserId.Split(":")[0][1..];
public string ServerName => UserId.Split(":", 2)[1];
+ public string BaseUrl => ClientHttpClient.BaseAddress!.ToString().TrimEnd('/');
[JsonIgnore]
public string AccessToken { get; set; }
- public HsNamedCaches NamedCaches { get; set; } = null!;
+ public HsNamedCaches NamedCaches { get; set; }
public GenericRoom GetRoom(string roomId) {
if (roomId is null || !roomId.StartsWith("!")) throw new ArgumentException("Room ID must start with !", nameof(roomId));
@@ -408,22 +409,22 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
#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 bool? _serverSupportsAuthMedia;
- 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<string> GetMediaUrlAsync(MxcUri mxcUri, string? filename = null, int? timeout = null) {
+ if (_serverSupportsAuthMedia == true) return mxcUri.ToDownloadUri(BaseUrl, filename, timeout);
+ if (_serverSupportsAuthMedia == false) return mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout);
- 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();
+ // Console.WriteLine($"Trying authenticated media URL: {uri}");
+ var res = await ClientHttpClient.SendAsync(new() {
+ Method = HttpMethod.Head,
+ RequestUri = (new Uri(mxcUri.ToDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute))
+ });
+ if (res.IsSuccessStatusCode) {
+ _serverSupportsAuthMedia = true;
+ return mxcUri.ToDownloadUri(BaseUrl, filename, timeout);
+ }
}
catch (MatrixException e) {
if (e is not { ErrorCode: "M_UNKNOWN" }) throw;
@@ -431,11 +432,15 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
//fallback to legacy media
try {
- var uri = $"/_matrix/media/v3/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();
+ // Console.WriteLine($"Trying legacy media URL: {uri}");
+ var res = await ClientHttpClient.SendAsync(new() {
+ Method = HttpMethod.Head,
+ RequestUri = new(mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout), string.IsNullOrWhiteSpace(BaseUrl) ? UriKind.Relative : UriKind.Absolute)
+ });
+ if (res.IsSuccessStatusCode) {
+ _serverSupportsAuthMedia = false;
+ return mxcUri.ToLegacyDownloadUri(BaseUrl, filename, timeout);
+ }
}
catch (MatrixException e) {
if (e is not { ErrorCode: "M_UNKNOWN" }) throw;
@@ -443,13 +448,19 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
throw new LibMatrixException() {
ErrorCode = LibMatrixException.ErrorCodes.M_UNSUPPORTED,
- Error = "Failed to download media"
+ Error = "Failed to get media URL"
};
- // return default;
}
- public async Task<Stream> GetThumbnailStreamAsync(string mxcUri, int width, int height, string? method = null, int? timeout = null) {
- var (serverName, mediaId) = ParseMxcUri(mxcUri);
+ public async Task<Stream> GetMediaStreamAsync(string mxcUri, string? filename = null, int? timeout = null) {
+ var uri = await GetMediaUrlAsync(mxcUri, filename, timeout);
+ var res = await ClientHttpClient.GetAsync(uri);
+ return await res.Content.ReadAsStreamAsync();
+ }
+
+ public async Task<Stream> GetThumbnailStreamAsync(MxcUri mxcUri, int width, int height, string? method = null, int? timeout = null) {
+ var (serverName, mediaId) = mxcUri;
+
try {
var uri = new Uri($"/_matrix/client/v1/thumbnail/{serverName}/{mediaId}");
uri = uri.AddQuery("width", width.ToString());
@@ -494,7 +505,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
catch (MatrixException e) {
if (e is not { ErrorCode: "M_UNRECOGNIZED" }) throw;
}
-
+
//fallback to legacy media
try {
var res = await ClientHttpClient.GetAsync($"/_matrix/media/v3/preview_url?url={HttpUtility.UrlEncode(url)}");
@@ -503,7 +514,7 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
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"
diff --git a/LibMatrix/Homeservers/FederationClient.cs b/LibMatrix/Homeservers/FederationClient.cs
index c7f0240..617b737 100644
--- a/LibMatrix/Homeservers/FederationClient.cs
+++ b/LibMatrix/Homeservers/FederationClient.cs
@@ -13,8 +13,8 @@ public class FederationClient {
if (proxy is not null) HttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", federationEndpoint);
}
- public MatrixHttpClient HttpClient { get; set; } = null!;
- public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } = null!;
+ public MatrixHttpClient HttpClient { get; set; }
+ public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; }
public async Task<ServerVersionResponse> GetServerVersionAsync() => await HttpClient.GetFromJsonAsync<ServerVersionResponse>("/_matrix/federation/v1/version");
}
diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs
index 7995f03..7ac54a7 100644
--- a/LibMatrix/Homeservers/RemoteHomeServer.cs
+++ b/LibMatrix/Homeservers/RemoteHomeServer.cs
@@ -10,24 +10,24 @@ using LibMatrix.Services;
namespace LibMatrix.Homeservers;
public class RemoteHomeserver {
- public RemoteHomeserver(string baseUrl, HomeserverResolverService.WellKnownUris wellKnownUris, string? proxy) {
+ public RemoteHomeserver(string serverName, HomeserverResolverService.WellKnownUris wellKnownUris, string? proxy) {
if (string.IsNullOrWhiteSpace(proxy))
proxy = null;
- BaseUrl = baseUrl;
+ ServerNameOrUrl = serverName;
WellKnownUris = wellKnownUris;
ClientHttpClient = new MatrixHttpClient {
- BaseAddress = new Uri(proxy?.TrimEnd('/') ?? wellKnownUris.Client?.TrimEnd('/') ?? throw new InvalidOperationException($"No client URI for {baseUrl}!")),
+ BaseAddress = new Uri(proxy?.TrimEnd('/') ?? wellKnownUris.Client?.TrimEnd('/') ?? throw new InvalidOperationException($"No client URI for {serverName}!")),
// Timeout = TimeSpan.FromSeconds(300) // TODO: Re-implement this
};
- if (proxy is not null) ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl);
+ if (proxy is not null) ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName);
if (!string.IsNullOrWhiteSpace(wellKnownUris.Server))
FederationClient = new FederationClient(WellKnownUris.Server!, proxy);
Auth = new(this);
}
private Dictionary<string, object> _profileCache { get; set; } = new();
- public string BaseUrl { get; }
+ public string ServerNameOrUrl { get; }
[JsonIgnore]
public MatrixHttpClient ClientHttpClient { get; set; }
@@ -111,8 +111,8 @@ public class RemoteHomeserver {
public class AliasResult {
[JsonPropertyName("room_id")]
- public string RoomId { get; set; } = null!;
+ public string RoomId { get; set; }
[JsonPropertyName("servers")]
- public List<string> Servers { get; set; } = null!;
+ public List<string> Servers { get; set; }
}
\ No newline at end of file
diff --git a/LibMatrix/Homeservers/UserInteractiveAuthClient.cs b/LibMatrix/Homeservers/UserInteractiveAuthClient.cs
index bb3889f..8de01d2 100644
--- a/LibMatrix/Homeservers/UserInteractiveAuthClient.cs
+++ b/LibMatrix/Homeservers/UserInteractiveAuthClient.cs
@@ -49,27 +49,27 @@ public class UserInteractiveAuthClient {
internal class RegisterFlowsResponse {
[JsonPropertyName("session")]
- public string Session { get; set; } = null!;
+ public string Session { get; set; }
[JsonPropertyName("flows")]
- public List<RegisterFlow> Flows { get; set; } = null!;
+ public List<RegisterFlow> Flows { get; set; }
[JsonPropertyName("params")]
- public JsonObject Params { get; set; } = null!;
+ public JsonObject Params { get; set; }
public class RegisterFlow {
[JsonPropertyName("stages")]
- public List<string> Stages { get; set; } = null!;
+ public List<string> Stages { get; set; }
}
}
internal class LoginFlowsResponse {
[JsonPropertyName("flows")]
- public List<LoginFlow> Flows { get; set; } = null!;
+ public List<LoginFlow> Flows { get; set; }
public class LoginFlow {
[JsonPropertyName("type")]
- public string Type { get; set; } = null!;
+ public string Type { get; set; }
}
}
diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj
index c074e81..4814a18 100644
--- a/LibMatrix/LibMatrix.csproj
+++ b/LibMatrix/LibMatrix.csproj
@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250123-182609" Condition="'$(Configuration)' == 'Release'"/>
+ <PackageReference Include="ArcaneLibs" Version="1.0.0-preview.20250208-191806" Condition="'$(Configuration)' == 'Release'" />
<ProjectReference Include="..\ArcaneLibs\ArcaneLibs\ArcaneLibs.csproj" Condition="'$(Configuration)' == 'Debug'"/>
</ItemGroup>
diff --git a/LibMatrix/MxcUri.cs b/LibMatrix/MxcUri.cs
new file mode 100644
index 0000000..02a8fa6
--- /dev/null
+++ b/LibMatrix/MxcUri.cs
@@ -0,0 +1,43 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace LibMatrix;
+
+public class MxcUri {
+ public required string ServerName;
+ public required string MediaId;
+
+ public static MxcUri Parse([StringSyntax("Uri")] 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 new MxcUri {
+ ServerName = parts[0],
+ MediaId = parts[1]
+ };
+ }
+
+ public static implicit operator MxcUri(string mxcUri) => Parse(mxcUri);
+ public static implicit operator string(MxcUri mxcUri) => $"mxc://{mxcUri.ServerName}/{mxcUri.MediaId}";
+ public static implicit operator (string, string)(MxcUri mxcUri) => (mxcUri.ServerName, mxcUri.MediaId);
+ public static implicit operator MxcUri((string serverName, string mediaId) mxcUri) => (mxcUri.serverName, mxcUri.mediaId);
+ // public override string ToString() => $"mxc://{ServerName}/{MediaId}";
+
+ public string ToDownloadUri(string? baseUrl = null, string? filename = null, int? timeout = null) {
+ var uri = $"{baseUrl}/_matrix/client/v1/media/download/{ServerName}/{MediaId}";
+ if (filename is not null) uri += $"/{filename}";
+ if (timeout is not null) uri += $"?timeout={timeout}";
+ return uri;
+ }
+
+ public string ToLegacyDownloadUri(string? baseUrl = null, string? filename = null, int? timeout = null) {
+ var uri = $"{baseUrl}/_matrix/media/v3/download/{ServerName}/{MediaId}";
+ if (filename is not null) uri += $"/{filename}";
+ if (timeout is not null) uri += $"?timeout_ms={timeout}";
+ return uri;
+ }
+
+ public void Deconstruct(out string serverName, out string mediaId) {
+ serverName = ServerName;
+ mediaId = MediaId;
+ }
+}
\ No newline at end of file
diff --git a/LibMatrix/Responses/CreateRoomRequest.cs b/LibMatrix/Responses/CreateRoomRequest.cs
index 1da6ff3..d9a6acd 100644
--- a/LibMatrix/Responses/CreateRoomRequest.cs
+++ b/LibMatrix/Responses/CreateRoomRequest.cs
@@ -26,7 +26,7 @@ public class CreateRoomRequest {
//we dont want to use this, we want more control
// [JsonPropertyName("preset")]
- // public string Preset { get; set; } = null!;
+ // public string Preset { get; set; }
[JsonPropertyName("initial_state")]
public List<StateEvent>? InitialState { get; set; }
diff --git a/LibMatrix/Responses/LoginResponse.cs b/LibMatrix/Responses/LoginResponse.cs
index b15eb20..ac2269c 100644
--- a/LibMatrix/Responses/LoginResponse.cs
+++ b/LibMatrix/Responses/LoginResponse.cs
@@ -4,10 +4,10 @@ namespace LibMatrix.Responses;
public class LoginResponse {
[JsonPropertyName("access_token")]
- public string AccessToken { get; set; } = null!;
+ public string AccessToken { get; set; }
[JsonPropertyName("device_id")]
- public string DeviceId { get; set; } = null!;
+ public string DeviceId { get; set; }
private string? _homeserver;
@@ -18,7 +18,7 @@ public class LoginResponse {
}
[JsonPropertyName("user_id")]
- public string UserId { get; set; } = null!;
+ public string UserId { get; set; }
// public async Task<AuthenticatedHomeserverGeneric> GetAuthenticatedHomeserver(string? proxy = null) {
// var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(Homeserver);
diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs
index b2308c5..529bd5c 100644
--- a/LibMatrix/Responses/SyncResponse.cs
+++ b/LibMatrix/Responses/SyncResponse.cs
@@ -8,7 +8,7 @@ internal partial class SyncResponseSerializerContext : JsonSerializerContext { }
public class SyncResponse {
[JsonPropertyName("next_batch")]
- public string NextBatch { get; set; } = null!;
+ public string NextBatch { get; set; }
[JsonPropertyName("account_data")]
public EventList? AccountData { get; set; }
@@ -17,7 +17,7 @@ public class SyncResponse {
public PresenceDataStructure? Presence { get; set; }
[JsonPropertyName("device_one_time_keys_count")]
- public Dictionary<string, int>? DeviceOneTimeKeysCount { get; set; } = null!;
+ public Dictionary<string, int>? DeviceOneTimeKeysCount { get; set; }
[JsonPropertyName("rooms")]
public RoomsDataStructure? Rooms { get; set; }
diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 202bfc1..8f1b56d 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -553,5 +553,5 @@ public class GenericRoom {
public class RoomIdResponse {
[JsonPropertyName("room_id")]
- public string RoomId { get; set; } = null!;
+ public string RoomId { get; set; }
}
\ No newline at end of file
|