diff --git a/MxApiExtensions/Controllers/Other/GenericProxyController.cs b/MxApiExtensions/Controllers/Other/GenericProxyController.cs
new file mode 100644
index 0000000..bae07c0
--- /dev/null
+++ b/MxApiExtensions/Controllers/Other/GenericProxyController.cs
@@ -0,0 +1,176 @@
+using System.Net.Http.Headers;
+using Microsoft.AspNetCore.Mvc;
+using MxApiExtensions.Classes.LibMatrix;
+using MxApiExtensions.Services;
+
+namespace MxApiExtensions.Controllers;
+
+[ApiController]
+[Route("/{*_}")]
+public class GenericController(ILogger<GenericController> logger, MxApiExtensionsConfiguration config, AuthenticationService authenticationService,
+ AuthenticatedHomeserverProviderService authenticatedHomeserverProviderService)
+ : ControllerBase {
+ [HttpGet]
+ public async Task Proxy([FromQuery] string? access_token, string? _) {
+ try {
+ // access_token ??= _authenticationService.GetToken(fail: false);
+ // var mxid = await _authenticationService.GetMxidFromToken(fail: false);
+ var hs = await authenticatedHomeserverProviderService.GetRemoteHomeserver();
+
+ logger.LogInformation("Proxying request: {}{}", Request.Path, Request.QueryString);
+
+ //remove access_token from query string
+ Request.QueryString = new QueryString(
+ Request.QueryString.Value?.Replace("&access_token", "access_token")
+ .Replace($"access_token={access_token}", "")
+ );
+
+ var resp = await hs.ClientHttpClient.GetAsync($"{Request.Path}{Request.QueryString}");
+
+ if (resp.Content is null) {
+ throw new MxApiMatrixException {
+ ErrorCode = "M_UNKNOWN",
+ Error = "No content in response"
+ };
+ }
+
+ Response.StatusCode = (int)resp.StatusCode;
+ Response.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json";
+ await Response.StartAsync();
+ await using var stream = await resp.Content.ReadAsStreamAsync();
+ await stream.CopyToAsync(Response.Body);
+ await Response.Body.FlushAsync();
+ await Response.CompleteAsync();
+ }
+ catch (MxApiMatrixException e) {
+ logger.LogError(e, "Matrix error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "application/json";
+
+ await Response.WriteAsync(e.GetAsJson());
+ await Response.CompleteAsync();
+ }
+ catch (Exception e) {
+ logger.LogError(e, "Unhandled error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "text/plain";
+
+ await Response.WriteAsync(e.ToString());
+ await Response.CompleteAsync();
+ }
+ }
+
+ [HttpPost]
+ public async Task ProxyPost([FromQuery] string? access_token, string _) {
+ try {
+ access_token ??= authenticationService.GetToken(fail: false);
+ var mxid = await authenticationService.GetMxidFromToken(fail: false);
+ var hs = await authenticatedHomeserverProviderService.GetHomeserver();
+
+ logger.LogInformation("Proxying request for {}: {}{}", mxid, Request.Path, Request.QueryString);
+
+ using var hc = new HttpClient();
+ hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", access_token);
+ hc.Timeout = TimeSpan.FromMinutes(10);
+ //remove access_token from query string
+ Request.QueryString = new QueryString(
+ Request.QueryString.Value
+ .Replace("&access_token", "access_token")
+ .Replace($"access_token={access_token}", "")
+ );
+
+ var resp = await hs.ClientHttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"{Request.Path}{Request.QueryString}") {
+ Method = HttpMethod.Post,
+ Content = new StreamContent(Request.Body)
+ });
+
+ if (resp.Content is null) {
+ throw new MxApiMatrixException {
+ ErrorCode = "M_UNKNOWN",
+ Error = "No content in response"
+ };
+ }
+
+ Response.StatusCode = (int)resp.StatusCode;
+ Response.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json";
+ await Response.StartAsync();
+ await using var stream = await resp.Content.ReadAsStreamAsync();
+ await stream.CopyToAsync(Response.Body);
+ await Response.Body.FlushAsync();
+ await Response.CompleteAsync();
+ }
+ catch (MxApiMatrixException e) {
+ logger.LogError(e, "Matrix error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "application/json";
+
+ await Response.WriteAsJsonAsync(e.GetAsJson());
+ await Response.CompleteAsync();
+ }
+ catch (Exception e) {
+ logger.LogError(e, "Unhandled error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "text/plain";
+
+ await Response.WriteAsync(e.ToString());
+ await Response.CompleteAsync();
+ }
+ }
+
+ [HttpPut]
+ public async Task ProxyPut([FromQuery] string? access_token, string _) {
+ try {
+ access_token ??= authenticationService.GetToken(fail: false);
+ var mxid = await authenticationService.GetMxidFromToken(fail: false);
+ var hs = await authenticatedHomeserverProviderService.GetHomeserver();
+
+ logger.LogInformation("Proxying request for {}: {}{}", mxid, Request.Path, Request.QueryString);
+
+ using var hc = new HttpClient();
+ hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", access_token);
+ hc.Timeout = TimeSpan.FromMinutes(10);
+ //remove access_token from query string
+ Request.QueryString = new QueryString(
+ Request.QueryString.Value
+ .Replace("&access_token", "access_token")
+ .Replace($"access_token={access_token}", "")
+ );
+
+ var resp = await hs.ClientHttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Put, $"{Request.Path}{Request.QueryString}") {
+ Method = HttpMethod.Put,
+ Content = new StreamContent(Request.Body)
+ });
+
+ if (resp.Content is null) {
+ throw new MxApiMatrixException {
+ ErrorCode = "M_UNKNOWN",
+ Error = "No content in response"
+ };
+ }
+
+ Response.StatusCode = (int)resp.StatusCode;
+ Response.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json";
+ await Response.StartAsync();
+ await using var stream = await resp.Content.ReadAsStreamAsync();
+ await stream.CopyToAsync(Response.Body);
+ await Response.Body.FlushAsync();
+ await Response.CompleteAsync();
+ }
+ catch (MxApiMatrixException e) {
+ logger.LogError(e, "Matrix error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "application/json";
+
+ await Response.WriteAsJsonAsync(e.GetAsJson());
+ await Response.CompleteAsync();
+ }
+ catch (Exception e) {
+ logger.LogError(e, "Unhandled error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "text/plain";
+
+ await Response.WriteAsync(e.ToString());
+ await Response.CompleteAsync();
+ }
+ }
+}
diff --git a/MxApiExtensions/Controllers/Other/MediaProxyController.cs b/MxApiExtensions/Controllers/Other/MediaProxyController.cs
new file mode 100644
index 0000000..03b68ba
--- /dev/null
+++ b/MxApiExtensions/Controllers/Other/MediaProxyController.cs
@@ -0,0 +1,78 @@
+using System.Net.Http.Headers;
+using LibMatrix.Homeservers;
+using LibMatrix.Services;
+using Microsoft.AspNetCore.Mvc;
+using MxApiExtensions.Classes.LibMatrix;
+using MxApiExtensions.Services;
+
+namespace MxApiExtensions.Controllers;
+
+[ApiController]
+[Route("/")]
+public class MediaProxyController(ILogger<GenericController> logger, MxApiExtensionsConfiguration config, AuthenticationService authenticationService,
+ AuthenticatedHomeserverProviderService authenticatedHomeserverProviderService, HomeserverProviderService hsProvider)
+ : ControllerBase {
+ private class MediaCacheEntry {
+ public DateTime LastRequested { get; set; } = DateTime.Now;
+ public byte[] Data { get; set; }
+ public string ContentType { get; set; }
+ public long Size => Data.LongCount();
+ }
+
+ private static Dictionary<string, MediaCacheEntry> _mediaCache = new();
+ private static SemaphoreSlim _semaphore = new(1, 1);
+
+ [HttpGet("/_matrix/media/{_}/download/{serverName}/{mediaId}")]
+ public async Task Proxy(string? _, string serverName, string mediaId) {
+ try {
+ logger.LogInformation("Proxying media: {}{}", serverName, mediaId);
+
+ await _semaphore.WaitAsync();
+ MediaCacheEntry entry;
+ if (!_mediaCache.ContainsKey($"{serverName}/{mediaId}")) {
+ _mediaCache.Add($"{serverName}/{mediaId}", entry = new());
+ List<RemoteHomeserver> FeasibleHomeservers = new();
+ {
+ var a = await authenticatedHomeserverProviderService.TryGetRemoteHomeserver();
+ if(a is not null)
+ FeasibleHomeservers.Add(a);
+ }
+
+ FeasibleHomeservers.Add(await hsProvider.GetRemoteHomeserver(serverName));
+
+ foreach (var homeserver in FeasibleHomeservers) {
+ var resp = await homeserver.ClientHttpClient.GetAsync($"{Request.Path}");
+ if(!resp.IsSuccessStatusCode) continue;
+ entry.ContentType = resp.Content.Headers.ContentType?.ToString() ?? "application/json";
+ entry.Data = await resp.Content.ReadAsByteArrayAsync();
+ break;
+ }
+ }
+ else entry = _mediaCache[$"{serverName}/{mediaId}"];
+ _semaphore.Release();
+
+ Response.StatusCode = 200;
+ Response.ContentType = entry.ContentType;
+ await Response.StartAsync();
+ await Response.Body.WriteAsync(entry.Data, 0, entry.Data.Length);
+ await Response.Body.FlushAsync();
+ await Response.CompleteAsync();
+ }
+ catch (MxApiMatrixException e) {
+ logger.LogError(e, "Matrix error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "application/json";
+
+ await Response.WriteAsync(e.GetAsJson());
+ await Response.CompleteAsync();
+ }
+ catch (Exception e) {
+ logger.LogError(e, "Unhandled error");
+ Response.StatusCode = StatusCodes.Status500InternalServerError;
+ Response.ContentType = "text/plain";
+
+ await Response.WriteAsync(e.ToString());
+ await Response.CompleteAsync();
+ }
+ }
+}
diff --git a/MxApiExtensions/Controllers/Other/WellKnownController.cs b/MxApiExtensions/Controllers/Other/WellKnownController.cs
new file mode 100644
index 0000000..c0e255f
--- /dev/null
+++ b/MxApiExtensions/Controllers/Other/WellKnownController.cs
@@ -0,0 +1,19 @@
+using System.Text.Json.Nodes;
+using Microsoft.AspNetCore.Mvc;
+
+namespace MxApiExtensions.Controllers;
+
+[ApiController]
+[Route("/")]
+public class WellKnownController(MxApiExtensionsConfiguration config) : ControllerBase {
+ private readonly MxApiExtensionsConfiguration _config = config;
+
+ [HttpGet("/.well-known/matrix/client")]
+ public object GetWellKnown() {
+ var res = new JsonObject();
+ res.Add("m.homeserver", new JsonObject {
+ { "base_url", Request.Scheme + "://" + Request.Host },
+ });
+ return res;
+ }
+}
|