diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
index 40fdef3..6be49b9 100644
--- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
+++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs
@@ -61,18 +61,15 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
return rooms;
}
- [Obsolete("Use UploadMedia instead, as this method is deprecated.")]
public virtual async Task<string> UploadFile(string fileName, IEnumerable<byte> data, string contentType = "application/octet-stream") {
return await UploadFile(fileName, data.ToArray(), contentType);
}
- [Obsolete("Use UploadMedia instead, as this method is deprecated.")]
public virtual async Task<string> UploadFile(string fileName, byte[] data, string contentType = "application/octet-stream") {
await using var ms = new MemoryStream(data);
return await UploadFile(fileName, ms, contentType);
}
- [Obsolete("Use UploadMedia instead, as this method is deprecated.")]
public virtual async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
var req = new HttpRequestMessage(HttpMethod.Post, $"/_matrix/media/v3/upload?filename={fileName}");
req.Content = new StreamContent(fileStream);
@@ -420,22 +417,100 @@ public class AuthenticatedHomeserverGeneric : RemoteHomeserver {
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, int timeout = 0) {
+
+ public async Task<Stream> GetMediaStreamAsync(string mxcUri, string? filename = null, int? timeout = null) {
var (serverName, mediaId) = ParseMxcUri(mxcUri);
try {
- var res = await ClientHttpClient.GetAsync($"/_matrix/client/v1/media/download/{serverName}/{mediaId}");
+ 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 (LibMatrixException e) {
- Console.WriteLine($"Failed to get media stream: {e.Message}");
- throw;
+ 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/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs
index 4641349..349ccb5 100644
--- a/LibMatrix/RoomTypes/GenericRoom.cs
+++ b/LibMatrix/RoomTypes/GenericRoom.cs
@@ -379,7 +379,7 @@ public class GenericRoom {
new UserIdAndReason { UserId = userId });
public async Task InviteUserAsync(string userId, string? reason = null, bool skipExisting = true) {
- if (skipExisting && await GetStateAsync<RoomMemberEventContent>("m.room.member", userId) is not null)
+ if (skipExisting && await GetStateOrNullAsync<RoomMemberEventContent>("m.room.member", userId) is not null)
return;
await Homeserver.ClientHttpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/invite", new UserIdAndReason(userId, reason));
}
diff --git a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
index c9727d6..13b5c1b 100644
--- a/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
+++ b/Tests/LibMatrix.Tests/Abstractions/HomeserverAbstraction.cs
@@ -1,14 +1,17 @@
using ArcaneLibs.Extensions;
using LibMatrix.Homeservers;
using LibMatrix.Responses;
+using LibMatrix.Services;
+using Microsoft.Extensions.Logging.Abstractions;
namespace LibMatrix.Tests.Abstractions;
public static class HomeserverAbstraction {
+ private static HomeserverResolverService _hsResolver = new HomeserverResolverService(NullLogger<HomeserverResolverService>.Instance);
+ private static HomeserverProviderService _hsProvider = new HomeserverProviderService(NullLogger<HomeserverProviderService>.Instance, _hsResolver);
+
public static async Task<AuthenticatedHomeserverGeneric> GetHomeserver() {
- var rhs = await RemoteHomeserver.Create("https://matrixunittests.rory.gay");
- // string username = Guid.NewGuid().ToString();
- // string password = Guid.NewGuid().ToString();
+ var rhs = await _hsProvider.GetRemoteHomeserver("matrixunittests.rory.gay");
var username = "@f1a2d2d6-1924-421b-91d0-893b347b2a49:matrixunittests.rory.gay";
var password = "d6d782d6-8bc9-4fac-9cd8-78e101b4298b";
LoginResponse reg;
@@ -23,8 +26,7 @@ public static class HomeserverAbstraction {
else throw new Exception("Failed to register", e);
}
- var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
-
+ var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken);
//var rooms = await hs.GetJoinedRooms();
// var disbandRoomTasks = rooms.Select(async room => {
@@ -45,9 +47,9 @@ public static class HomeserverAbstraction {
}
public static async Task<AuthenticatedHomeserverGeneric> GetRandomHomeserver() {
- var rhs = await RemoteHomeserver.Create("https://matrixunittests.rory.gay");
+ var rhs = await _hsProvider.GetRemoteHomeserver("matrixunittests.rory.gay");
var reg = await rhs.RegisterAsync(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "Unit tests!");
- var hs = await reg.GetAuthenticatedHomeserver("https://matrixunittests.rory.gay");
+ var hs = await _hsProvider.GetAuthenticatedWithToken(reg.Homeserver, reg.AccessToken);
// var rooms = await hs.GetJoinedRooms();
//
diff --git a/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs b/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs
new file mode 100644
index 0000000..b2f5627
--- /dev/null
+++ b/Tests/LibMatrix.Tests/Tests/AuthMediaTests.cs
@@ -0,0 +1,56 @@
+using ArcaneLibs.Extensions;
+using ArcaneLibs.Extensions.Streams;
+using LibMatrix.Homeservers;
+using LibMatrix.Services;
+using LibMatrix.Tests.Abstractions;
+using LibMatrix.Tests.Fixtures;
+using Xunit.Abstractions;
+using Xunit.Microsoft.DependencyInjection.Abstracts;
+
+namespace LibMatrix.Tests.Tests;
+
+public class AuthMediaTests : TestBed<TestFixture> {
+ private readonly TestFixture _fixture;
+ private readonly HomeserverResolverService _resolver;
+ private readonly Config _config;
+ private readonly HomeserverProviderService _provider;
+
+ public AuthMediaTests(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+ _fixture = fixture;
+ _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
+ _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
+ _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
+ }
+
+ private async Task<AuthenticatedHomeserverGeneric> GetHomeserver() => await HomeserverAbstraction.GetHomeserver();
+
+ [Fact]
+ public async Task UploadFileAsync() {
+ var hs = await HomeserverAbstraction.GetHomeserver();
+
+ var mxcUri = await hs.UploadFile("test", "LibMatrix test file".AsBytes());
+ Assert.NotNull(mxcUri);
+ }
+
+ [Fact]
+ public async Task DownloadFileAsync() {
+ var hs = await HomeserverAbstraction.GetHomeserver();
+
+ var mxcUri = await hs.UploadFile("test", "LibMatrix test file".AsBytes());
+ Assert.NotNull(mxcUri);
+
+ var file = await hs.GetMediaStreamAsync(mxcUri);
+ Assert.NotNull(file);
+
+ var data = file!.ReadToEnd().AsString();
+ Assert.Equal("LibMatrix test file", data);
+ }
+
+ [SkippableFact(typeof(LibMatrixException))] // This test will fail if the homeserver does not support URL previews
+ public async Task GetUrlPreviewAsync() {
+ var hs = await HomeserverAbstraction.GetHomeserver();
+ var preview = await hs.GetUrlPreviewAsync("https://matrix.org");
+
+ Assert.NotNull(preview);
+ }
+}
\ No newline at end of file
diff --git a/Tests/LibMatrix.Tests/Tests/AuthTests.cs b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
index f331dd0..3ffadf0 100644
--- a/Tests/LibMatrix.Tests/Tests/AuthTests.cs
+++ b/Tests/LibMatrix.Tests/Tests/AuthTests.cs
@@ -26,7 +26,13 @@ public class AuthTests : TestBed<TestFixture> {
Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
// var server = await _resolver.ResolveHomeserverFromWellKnown(_config.TestHomeserver!);
- var login = await _provider.Login(_config.TestHomeserver!, _config.TestUsername!, _config.TestPassword!);
+ var rhs = await _provider.GetRemoteHomeserver(_config.TestHomeserver);
+ var username = Guid.NewGuid().ToString();
+ var password = Guid.NewGuid().ToString();
+
+ var reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+
+ var login = await _provider.Login(_config.TestHomeserver!, username, password);
Assert.NotNull(login);
var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
Assert.NotNull(hs);
@@ -40,10 +46,13 @@ public class AuthTests : TestBed<TestFixture> {
Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
// var server = await _resolver.ResolveHomeserverFromWellKnown(_config.TestHomeserver!);
- var login = await _provider.Login(_config.TestHomeserver!, _config.TestUsername!, _config.TestPassword!);
- Assert.NotNull(login);
-
- var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, login.AccessToken);
+ var rhs = await _provider.GetRemoteHomeserver(_config.TestHomeserver);
+ var username = Guid.NewGuid().ToString();
+ var password = Guid.NewGuid().ToString();
+
+ var reg = await rhs.RegisterAsync(username, password, "Unit tests!");
+
+ var hs = await _provider.GetAuthenticatedWithToken(_config.TestHomeserver!, reg.AccessToken);
Assert.NotNull(hs);
Assert.NotNull(hs.WhoAmI);
hs.WhoAmI.VerifyRequiredFields();
diff --git a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
index 7fc7c64..d9bea94 100644
--- a/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
+++ b/Tests/LibMatrix.Tests/Tests/TestCleanup.cs
@@ -1,74 +1,74 @@
-using System.Diagnostics;
-using LibMatrix.Helpers;
-using LibMatrix.Services;
-using LibMatrix.Tests.Abstractions;
-using LibMatrix.Tests.Fixtures;
-using Microsoft.Extensions.Logging;
-using Xunit.Abstractions;
-using Xunit.Microsoft.DependencyInjection.Abstracts;
-
-namespace LibMatrix.Tests.Tests;
-
-public class TestCleanup : TestBed<TestFixture> {
- // private readonly TestFixture _fixture;
- private readonly HomeserverResolverService _resolver;
- private readonly Config _config;
- private readonly HomeserverProviderService _provider;
- private readonly ILogger<TestCleanup> _logger;
-
- public TestCleanup(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
- // _fixture = fixture;
- _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
- _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
- _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
- _logger = _fixture.GetService<ILogger<TestCleanup>>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(ILogger<TestCleanup>)}");
- }
-
- [Fact]
- public async Task Cleanup() {
- Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver), $"{nameof(_config.TestHomeserver)} must be set in appsettings!");
- Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername), $"{nameof(_config.TestUsername)} must be set in appsettings!");
- Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
-
- var hs = await HomeserverAbstraction.GetHomeserver();
- Assert.NotNull(hs);
-
- var syncHelper = new SyncHelper(hs, _logger) {
- Timeout = 3000
- };
- _testOutputHelper.WriteLine("Starting sync loop");
- var cancellationTokenSource = new CancellationTokenSource();
- var sw = Stopwatch.StartNew();
- syncHelper.SyncReceivedHandlers.Add(async response => {
- if (sw.ElapsedMilliseconds >= 3000) {
- _testOutputHelper.WriteLine("Cancelling sync loop");
-
- var tasks = (await hs.GetJoinedRooms()).Select(async room => {
- _logger.LogInformation("Leaving room: {}", room.RoomId);
- await room.LeaveAsync();
- await room.ForgetAsync();
- return room;
- }).ToList();
- await Task.WhenAll(tasks);
-
- cancellationTokenSource.Cancel();
- }
-
- sw.Restart();
- if (response.Rooms?.Leave is { Count: > 0 }) {
- // foreach (var room in response.Rooms.Leave) {
- // await hs.GetRoom(room.Key).ForgetAsync();
- // }
- var tasks = response.Rooms.Leave.Select(async room => {
- await hs.GetRoom(room.Key).ForgetAsync();
- return room;
- }).ToList();
- await Task.WhenAll(tasks);
- }
- });
- await syncHelper.RunSyncLoopAsync(cancellationToken: cancellationTokenSource.Token);
-
- Assert.NotNull(hs);
- await hs.Logout();
- }
-}
\ No newline at end of file
+// using System.Diagnostics;
+// using LibMatrix.Helpers;
+// using LibMatrix.Services;
+// using LibMatrix.Tests.Abstractions;
+// using LibMatrix.Tests.Fixtures;
+// using Microsoft.Extensions.Logging;
+// using Xunit.Abstractions;
+// using Xunit.Microsoft.DependencyInjection.Abstracts;
+//
+// namespace LibMatrix.Tests.Tests;
+//
+// public class TestCleanup : TestBed<TestFixture> {
+// // private readonly TestFixture _fixture;
+// private readonly HomeserverResolverService _resolver;
+// private readonly Config _config;
+// private readonly HomeserverProviderService _provider;
+// private readonly ILogger<TestCleanup> _logger;
+//
+// public TestCleanup(ITestOutputHelper testOutputHelper, TestFixture fixture) : base(testOutputHelper, fixture) {
+// // _fixture = fixture;
+// _resolver = _fixture.GetService<HomeserverResolverService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverResolverService)}");
+// _config = _fixture.GetService<Config>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(Config)}");
+// _provider = _fixture.GetService<HomeserverProviderService>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(HomeserverProviderService)}");
+// _logger = _fixture.GetService<ILogger<TestCleanup>>(_testOutputHelper) ?? throw new InvalidOperationException($"Failed to get {nameof(ILogger<TestCleanup>)}");
+// }
+//
+// [Fact]
+// public async Task Cleanup() {
+// Assert.False(string.IsNullOrWhiteSpace(_config.TestHomeserver), $"{nameof(_config.TestHomeserver)} must be set in appsettings!");
+// Assert.False(string.IsNullOrWhiteSpace(_config.TestUsername), $"{nameof(_config.TestUsername)} must be set in appsettings!");
+// Assert.False(string.IsNullOrWhiteSpace(_config.TestPassword), $"{nameof(_config.TestPassword)} must be set in appsettings!");
+//
+// var hs = await HomeserverAbstraction.GetHomeserver();
+// Assert.NotNull(hs);
+//
+// var syncHelper = new SyncHelper(hs, _logger) {
+// Timeout = 3000
+// };
+// _testOutputHelper.WriteLine("Starting sync loop");
+// var cancellationTokenSource = new CancellationTokenSource();
+// var sw = Stopwatch.StartNew();
+// syncHelper.SyncReceivedHandlers.Add(async response => {
+// if (sw.ElapsedMilliseconds >= 3000) {
+// _testOutputHelper.WriteLine("Cancelling sync loop");
+//
+// var tasks = (await hs.GetJoinedRooms()).Select(async room => {
+// _logger.LogInformation("Leaving room: {}", room.RoomId);
+// await room.LeaveAsync();
+// await room.ForgetAsync();
+// return room;
+// }).ToList();
+// await Task.WhenAll(tasks);
+//
+// cancellationTokenSource.Cancel();
+// }
+//
+// sw.Restart();
+// if (response.Rooms?.Leave is { Count: > 0 }) {
+// // foreach (var room in response.Rooms.Leave) {
+// // await hs.GetRoom(room.Key).ForgetAsync();
+// // }
+// var tasks = response.Rooms.Leave.Select(async room => {
+// await hs.GetRoom(room.Key).ForgetAsync();
+// return room;
+// }).ToList();
+// await Task.WhenAll(tasks);
+// }
+// });
+// await syncHelper.RunSyncLoopAsync(cancellationToken: cancellationTokenSource.Token);
+//
+// Assert.NotNull(hs);
+// await hs.Logout();
+// }
+// }
\ No newline at end of file
|