diff --git a/MatrixRoomUtils.Core/Attributes/TraceAttribute.cs b/MatrixRoomUtils.Core/Attributes/TraceAttribute.cs
deleted file mode 100644
index 34a0b67..0000000
--- a/MatrixRoomUtils.Core/Attributes/TraceAttribute.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Runtime.CompilerServices;
-
-namespace MatrixRoomUtils.Core.Attributes;
-
-[AttributeUsage(AttributeTargets.All)]
-public class TraceAttribute : Attribute {
- public TraceAttribute([CallerMemberName] string callerName = "") {
- Console.WriteLine($"{callerName} called!");
- }
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
index 10bbb36..09da766 100644
--- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -28,7 +28,7 @@ public class AuthenticatedHomeServer : IHomeServer {
}
public WhoAmIResponse WhoAmI { get; set; } = null!;
- public string UserId { get; }
+ public string UserId => WhoAmI.UserId;
public string AccessToken { get; set; }
@@ -80,7 +80,7 @@ public class AuthenticatedHomeServer : IHomeServer {
var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, 100)}&dir={dir}&order_by={orderBy}";
if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}";
- if (res?.NextBatch != null) url += $"&from={res.NextBatch}";
+ if (res?.NextBatch is not null) url += $"&from={res.NextBatch}";
Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---");
@@ -88,7 +88,7 @@ public class AuthenticatedHomeServer : IHomeServer {
totalRooms ??= res?.TotalRooms;
Console.WriteLine(res.ToJson(false));
foreach (var room in res.Rooms) {
- if (localFilter != null) {
+ if (localFilter is not null) {
if (!room.RoomId.Contains(localFilter.RoomIdContains)) {
totalRooms--;
continue;
@@ -144,7 +144,7 @@ public class AuthenticatedHomeServer : IHomeServer {
continue;
}
}
- // if (contentSearch != null && !string.IsNullOrEmpty(contentSearch) &&
+ // if (contentSearch is not null && !string.IsNullOrEmpty(contentSearch) &&
// !(
// room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
// room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
deleted file mode 100644
index 28e0c49..0000000
--- a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Net.Http.Json;
-using System.Text.Json;
-using MatrixRoomUtils.Core.Extensions;
-using MatrixRoomUtils.Core.Responses;
-using MatrixRoomUtils.Core.StateEventTypes;
-
-namespace MatrixRoomUtils.Core.Authentication;
-
-public class MatrixAuth {
- [Obsolete("This is possibly broken and should not be used.", true)]
- public static async Task<LoginResponse> Login(string homeserver, string username, string password) {
- Console.WriteLine($"Logging in to {homeserver} as {username}...");
- homeserver = (new RemoteHomeServer(homeserver)).FullHomeServerDomain;
- var hc = new MatrixHttpClient();
- var payload = new {
- type = "m.login.password",
- identifier = new {
- type = "m.id.user",
- user = username
- },
- password,
- initial_device_display_name = "Rory&::MatrixRoomUtils"
- };
- Console.WriteLine($"Sending login request to {homeserver}...");
- var resp = await hc.PostAsJsonAsync($"{homeserver}/_matrix/client/v3/login", payload);
- Console.WriteLine($"Login: {resp.StatusCode}");
- var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
- if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data);
-
- Console.WriteLine($"Login: {data.ToJson()}");
- return data.Deserialize<LoginResponse>();
- //var token = data.GetProperty("access_token").GetString();
- //return token;
- }
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
index 47b3121..852e1d8 100644
--- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
@@ -1,3 +1,4 @@
+using System.Reflection;
using System.Text.Json;
namespace MatrixRoomUtils.Core.Extensions;
@@ -18,23 +19,35 @@ public static class HttpClientExtensions {
public class MatrixHttpClient : HttpClient {
public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
+ try
+ {
+ HttpRequestOptionsKey<bool> WebAssemblyEnableStreamingResponseKey = new HttpRequestOptionsKey<bool>("WebAssemblyEnableStreamingResponse");
+ request.Options.Set(WebAssemblyEnableStreamingResponseKey, true);
+ // var asm = Assembly.Load("Microsoft.AspNetCore.Components.WebAssembly");
+ // var browserHttpHandlerType = asm.GetType("Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequestMessageExtensions", true);
+ // var browserHttpHandlerMethod = browserHttpHandlerType.GetMethod("SetBrowserResponseStreamingEnabled", BindingFlags.Public | BindingFlags.Static);
+ // browserHttpHandlerMethod?.Invoke(null, new object[] {request, true});
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Failed to set browser response streaming:");
+ Console.WriteLine(e);
+ }
var a = await base.SendAsync(request, cancellationToken);
if (!a.IsSuccessStatusCode) {
Console.WriteLine($"Failed to send request: {a.StatusCode}");
var content = await a.Content.ReadAsStringAsync(cancellationToken);
if (content.StartsWith('{')) {
var ex = JsonSerializer.Deserialize<MatrixException>(content);
- if (ex?.RetryAfterMs != null) {
+ if (ex?.RetryAfterMs is not null) {
await Task.Delay(ex.RetryAfterMs.Value, cancellationToken);
+ typeof(HttpRequestMessage).GetField("_sendStatus", BindingFlags.NonPublic | BindingFlags.Instance)?.SetValue(request, 0);
return await SendAsync(request, cancellationToken);
}
-
throw ex!;
}
-
throw new InvalidDataException("Encountered invalid data:\n" + content);
}
-
return a;
}
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Helpers/MediaResolver.cs b/MatrixRoomUtils.Core/Helpers/MediaResolver.cs
new file mode 100644
index 0000000..0869135
--- /dev/null
+++ b/MatrixRoomUtils.Core/Helpers/MediaResolver.cs
@@ -0,0 +1,6 @@
+namespace MatrixRoomUtils.Core.Helpers;
+
+public class MediaResolver {
+ public static string ResolveMediaUri(string homeserver, string mxc) =>
+ mxc.Replace("mxc://", $"{homeserver}/_matrix/media/v3/download/");
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Helpers/SyncHelper.cs b/MatrixRoomUtils.Core/Helpers/SyncHelper.cs
index 1b9c598..eff412b 100644
--- a/MatrixRoomUtils.Core/Helpers/SyncHelper.cs
+++ b/MatrixRoomUtils.Core/Helpers/SyncHelper.cs
@@ -20,7 +20,7 @@ public class SyncHelper {
public async Task<SyncResult?> Sync(string? since = null, CancellationToken? cancellationToken = null) {
var outFileName = "sync-" +
- (await _storageService.CacheStorageProvider.GetAllKeys()).Count(x => x.StartsWith("sync")) +
+ (await _storageService.CacheStorageProvider.GetAllKeysAsync()).Count(x => x.StartsWith("sync")) +
".json";
var url = "/_matrix/client/v3/sync?timeout=30000&set_presence=online";
if (!string.IsNullOrWhiteSpace(since)) url += $"&since={since}";
@@ -29,7 +29,7 @@ public class SyncHelper {
try {
var res = await _homeServer._httpClient.GetFromJsonAsync<SyncResult>(url,
cancellationToken: cancellationToken ?? CancellationToken.None);
- await _storageService.CacheStorageProvider.SaveObject(outFileName, res);
+ await _storageService.CacheStorageProvider.SaveObjectAsync(outFileName, res);
Console.WriteLine($"Wrote file: {outFileName}");
return res;
}
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index 4fa6c1b..4ee2a3e 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -6,34 +6,36 @@ using MatrixRoomUtils.Core.StateEventTypes;
namespace MatrixRoomUtils.Core.Interfaces;
public class IHomeServer {
- private readonly Dictionary<string, ProfileResponse?> _profileCache = new();
+ private readonly Dictionary<string, object> _profileCache = new();
public string HomeServerDomain { get; set; }
public string FullHomeServerDomain { get; set; }
protected internal MatrixHttpClient _httpClient { get; set; } = new();
-
public async Task<ProfileResponse> GetProfile(string mxid, bool debounce = false, bool cache = true) {
- if (cache) {
- if (debounce) await Task.Delay(Random.Shared.Next(100, 500));
- if (_profileCache.ContainsKey(mxid)) {
- while (_profileCache[mxid] == null) {
- Console.WriteLine($"Waiting for profile cache for {mxid}, currently {_profileCache[mxid]?.ToJson() ?? "null"} within {_profileCache.Count} profiles...");
- await Task.Delay(Random.Shared.Next(50, 500));
- }
-
- return _profileCache[mxid];
- }
+ // if (cache) {
+ // if (debounce) await Task.Delay(Random.Shared.Next(100, 500));
+ // if (_profileCache.ContainsKey(mxid)) {
+ // while (_profileCache[mxid] == null) {
+ // Console.WriteLine($"Waiting for profile cache for {mxid}, currently {_profileCache[mxid]?.ToJson() ?? "null"} within {_profileCache.Count} profiles...");
+ // await Task.Delay(Random.Shared.Next(50, 500));
+ // }
+ //
+ // return _profileCache[mxid];
+ // }
+ // }
+ if(mxid is null) throw new ArgumentNullException(nameof(mxid));
+ if (_profileCache.ContainsKey(mxid)) {
+ if (_profileCache[mxid] is SemaphoreSlim s) await s.WaitAsync();
+ if (_profileCache[mxid] is ProfileResponse p) return p;
}
-
- _profileCache.Add(mxid, null);
+ _profileCache[mxid] = new SemaphoreSlim(1);
+
var resp = await _httpClient.GetAsync($"/_matrix/client/v3/profile/{mxid}");
- var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
+ var data = await resp.Content.ReadFromJsonAsync<ProfileResponse>();
if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
- var profile = data.Deserialize<ProfileResponse>();
- _profileCache[mxid] = profile;
- return profile;
+ _profileCache[mxid] = data;
+
+ return data;
}
-
- public string? ResolveMediaUri(string mxc) => mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/v3/download/");
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs b/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs
index 01314da..eefb79c 100644
--- a/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs
+++ b/MatrixRoomUtils.Core/Interfaces/Services/IStorageProvider.cs
@@ -2,45 +2,45 @@ namespace MatrixRoomUtils.Core.Interfaces.Services;
public interface IStorageProvider {
// save all children of a type with reflection
- public Task SaveAllChildren<T>(string key, T value) {
+ public Task SaveAllChildrenAsync<T>(string key, T value) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement SaveAllChildren<T>(key, value)!");
- return Task.CompletedTask;
+ throw new NotImplementedException();
}
// load all children of a type with reflection
- public Task<T?> LoadAllChildren<T>(string key) {
+ public Task<T?> LoadAllChildrenAsync<T>(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement LoadAllChildren<T>(key)!");
- return Task.FromResult(default(T));
+ throw new NotImplementedException();
}
- public Task SaveObject<T>(string key, T value) {
+ public Task SaveObjectAsync<T>(string key, T value) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement SaveObject<T>(key, value)!");
- return Task.CompletedTask;
+ throw new NotImplementedException();
}
// load
- public Task<T?> LoadObject<T>(string key) {
+ public Task<T?> LoadObjectAsync<T>(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement LoadObject<T>(key)!");
- return Task.FromResult(default(T));
+ throw new NotImplementedException();
}
// check if exists
- public Task<bool> ObjectExists(string key) {
+ public Task<bool> ObjectExistsAsync(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement ObjectExists(key)!");
- return Task.FromResult(false);
+ throw new NotImplementedException();
}
// get all keys
- public Task<List<string>> GetAllKeys() {
+ public Task<List<string>> GetAllKeysAsync() {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement GetAllKeys()!");
- return Task.FromResult(new List<string>());
+ throw new NotImplementedException();
}
// delete
- public Task DeleteObject(string key) {
+ public Task DeleteObjectAsync(string key) {
Console.WriteLine($"StorageProvider<{GetType().Name}> does not implement DeleteObject(key)!");
- return Task.CompletedTask;
+ throw new NotImplementedException();
}
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RemoteHomeServer.cs b/MatrixRoomUtils.Core/RemoteHomeServer.cs
index 9c5f2a3..e6c28c3 100644
--- a/MatrixRoomUtils.Core/RemoteHomeServer.cs
+++ b/MatrixRoomUtils.Core/RemoteHomeServer.cs
@@ -2,6 +2,7 @@ using System.Net.Http.Json;
using System.Text.Json;
using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Interfaces;
+using MatrixRoomUtils.Core.Services;
namespace MatrixRoomUtils.Core;
diff --git a/MatrixRoomUtils.Core/Responses/LoginResponse.cs b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
index b248739..239ea03 100644
--- a/MatrixRoomUtils.Core/Responses/LoginResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
@@ -10,7 +10,7 @@ public class LoginResponse {
public string DeviceId { get; set; }
[JsonPropertyName("home_server")]
- public string HomeServer { get; set; }
+ public string Homeserver { get; set; }
[JsonPropertyName("user_id")]
public string UserId { get; set; }
diff --git a/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs b/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs
index 8dc30d1..f57c855 100644
--- a/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs
+++ b/MatrixRoomUtils.Core/RoomTypes/GenericRoom.cs
@@ -1,15 +1,17 @@
using System.Net.Http.Json;
-using System.Text;
using System.Text.Json;
using System.Web;
using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Responses;
using MatrixRoomUtils.Core.RoomTypes;
+using MatrixRoomUtils.Core.StateEventTypes;
+using Microsoft.Extensions.Logging;
namespace MatrixRoomUtils.Core;
public class GenericRoom {
internal readonly AuthenticatedHomeServer _homeServer;
- internal readonly HttpClient _httpClient;
+ internal readonly MatrixHttpClient _httpClient;
public GenericRoom(AuthenticatedHomeServer homeServer, string roomId) {
_homeServer = homeServer;
@@ -21,51 +23,41 @@ public class GenericRoom {
public string RoomId { get; set; }
- public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "", bool logOnFailure = true) {
+ [Obsolete("", true)]
+ public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "") {
var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
if (!string.IsNullOrEmpty(type)) url += $"/{type}";
if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}";
+ return await _httpClient.GetFromJsonAsync<JsonElement>(url);
+ }
- var res = await _httpClient.GetAsync(url);
- if (!res.IsSuccessStatusCode) {
- if (logOnFailure) Console.WriteLine($"{RoomId}/{stateKey}/{type} - got status: {res.StatusCode}");
- return null;
+ public async IAsyncEnumerable<StateEventResponse?> GetFullStateAsync() {
+ var res = await _httpClient.GetAsync($"/_matrix/client/v3/rooms/{RoomId}/state");
+ var result =
+ JsonSerializer.DeserializeAsyncEnumerable<StateEventResponse>(await res.Content.ReadAsStreamAsync());
+ await foreach (var resp in result) {
+ yield return resp;
}
-
- var result = await res.Content.ReadFromJsonAsync<JsonElement>();
- return result;
}
- public async Task<T?> GetStateAsync<T>(string type, string stateKey = "", bool logOnFailure = false) {
- var res = await GetStateAsync(type, stateKey, logOnFailure);
- if (res == null) return default;
- return res.Value.Deserialize<T>();
+ public async Task<T?> GetStateAsync<T>(string type, string stateKey = "") {
+ var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
+ if (!string.IsNullOrEmpty(type)) url += $"/{type}";
+ if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}";
+ return await _httpClient.GetFromJsonAsync<T>(url);
}
public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b",
string filter = "") {
var url = $"/_matrix/client/v3/rooms/{RoomId}/messages?from={from}&limit={limit}&dir={dir}";
if (!string.IsNullOrEmpty(filter)) url += $"&filter={filter}";
- var res = await _httpClient.GetAsync(url);
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to get messages for {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to get messages for {RoomId} - got status: {res.StatusCode}");
- }
-
- var result = await res.Content.ReadFromJsonAsync<MessagesResponse>();
- return result ?? new MessagesResponse();
+ var res = await _httpClient.GetFromJsonAsync<MessagesResponse>(url);
+ return res ?? new MessagesResponse();
}
public async Task<string> GetNameAsync() {
- var res = await GetStateAsync("m.room.name");
- if (!res.HasValue) {
- Console.WriteLine($"Room {RoomId} has no name!");
- return RoomId;
- }
-
- var resn = res?.TryGetProperty("name", out var name) ?? false ? name.GetString() ?? RoomId : RoomId;
- //Console.WriteLine($"Got name: {resn}");
- return resn;
+ var res = await GetStateAsync<RoomNameEventData>("m.room.name");
+ return res.Name ?? RoomId;
}
public async Task JoinAsync(string[]? homeservers = null, string? reason = null) {
@@ -78,165 +70,89 @@ public class GenericRoom {
});
}
- public async Task<List<string>> GetMembersAsync(bool joinedOnly = true) {
- var res = await GetStateAsync("");
- if (!res.HasValue) return new List<string>();
- var members = new List<string>();
- foreach (var member in res.Value.EnumerateArray()) {
- if (member.GetProperty("type").GetString() != "m.room.member") continue;
- if (joinedOnly && member.GetProperty("content").GetProperty("membership").GetString() != "join") continue;
- var memberId = member.GetProperty("state_key").GetString();
- members.Add(
- memberId ?? throw new InvalidOperationException("Event type was member but state key was null!"));
+ public async IAsyncEnumerable<StateEventResponse> GetMembersAsync(bool joinedOnly = true) {
+ var res = GetFullStateAsync();
+ await foreach (var member in res) {
+ if (member.Type != "m.room.member") continue;
+ if (joinedOnly && (member.TypedContent as RoomMemberEventData).Membership is not "join") continue;
+ yield return member;
}
-
- return members;
}
public async Task<List<string>> GetAliasesAsync() {
- var res = await GetStateAsync("m.room.aliases");
- if (!res.HasValue) return new List<string>();
- var aliases = new List<string>();
- foreach (var alias in res.Value.GetProperty("aliases").EnumerateArray()) aliases.Add(alias.GetString() ?? "");
-
- return aliases;
+ var res = await GetStateAsync<RoomAliasEventData>("m.room.aliases");
+ return res.Aliases;
}
- public async Task<string> GetCanonicalAliasAsync() {
- var res = await GetStateAsync("m.room.canonical_alias");
- if (!res.HasValue) return "";
- return res.Value.GetProperty("alias").GetString() ?? "";
- }
-
- public async Task<string> GetTopicAsync() {
- var res = await GetStateAsync("m.room.topic");
- if (!res.HasValue) return "";
- return res.Value.GetProperty("topic").GetString() ?? "";
- }
-
- public async Task<string> GetAvatarUrlAsync() {
- var res = await GetStateAsync("m.room.avatar");
- if (!res.HasValue) return "";
- return res.Value.GetProperty("url").GetString() ?? "";
- }
+ public async Task<CanonicalAliasEventData?> GetCanonicalAliasAsync() =>
+ await GetStateAsync<CanonicalAliasEventData>("m.room.canonical_alias");
- public async Task<JoinRulesEventData> GetJoinRuleAsync() {
- var res = await GetStateAsync("m.room.join_rules");
- if (!res.HasValue) return new JoinRulesEventData();
- return res.Value.Deserialize<JoinRulesEventData>() ?? new JoinRulesEventData();
- }
+ public async Task<RoomTopicEventData?> GetTopicAsync() =>
+ await GetStateAsync<RoomTopicEventData>("m.room.topic");
- public async Task<string> GetHistoryVisibilityAsync() {
- var res = await GetStateAsync("m.room.history_visibility");
- if (!res.HasValue) return "";
- return res.Value.GetProperty("history_visibility").GetString() ?? "";
- }
+ public async Task<RoomAvatarEventData?> GetAvatarUrlAsync() =>
+ await GetStateAsync<RoomAvatarEventData>("m.room.avatar");
- public async Task<string> GetGuestAccessAsync() {
- var res = await GetStateAsync("m.room.guest_access");
- if (!res.HasValue) return "";
- return res.Value.GetProperty("guest_access").GetString() ?? "";
- }
+ public async Task<JoinRulesEventData> GetJoinRuleAsync() =>
+ await GetStateAsync<JoinRulesEventData>("m.room.join_rules");
- public async Task<CreateEvent> GetCreateEventAsync() {
- var res = await GetStateAsync("m.room.create");
- if (!res.HasValue) return new CreateEvent();
+ public async Task<HistoryVisibilityData?> GetHistoryVisibilityAsync() =>
+ await GetStateAsync<HistoryVisibilityData>("m.room.history_visibility");
- res.FindExtraJsonElementFields(typeof(CreateEvent));
+ public async Task<GuestAccessData?> GetGuestAccessAsync() =>
+ await GetStateAsync<GuestAccessData>("m.room.guest_access");
- return res.Value.Deserialize<CreateEvent>() ?? new CreateEvent();
- }
+ public async Task<CreateEvent> GetCreateEventAsync() =>
+ await GetStateAsync<CreateEvent>("m.room.create");
public async Task<string?> GetRoomType() {
- var res = await GetStateAsync("m.room.create");
- if (!res.HasValue) return null;
- if (res.Value.TryGetProperty("type", out var type)) return type.GetString();
- return null;
+ var res = await GetStateAsync<RoomCreateEventData>("m.room.create");
+ return res.Type;
}
- public async Task ForgetAsync() {
- var res = await _httpClient.PostAsync($"/_matrix/client/v3/rooms/{RoomId}/forget", null);
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to forget room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to forget room {RoomId} - got status: {res.StatusCode}");
- }
- }
+ public async Task ForgetAsync() =>
+ await _httpClient.PostAsync($"/_matrix/client/v3/rooms/{RoomId}/forget", null);
- public async Task LeaveAsync(string? reason = null) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/leave", new {
+ public async Task LeaveAsync(string? reason = null) =>
+ await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/leave", new {
reason
});
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to leave room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to leave room {RoomId} - got status: {res.StatusCode}");
- }
- }
- public async Task KickAsync(string userId, string? reason = null) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/kick",
+ public async Task KickAsync(string userId, string? reason = null) =>
+ await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/kick",
new UserIdAndReason() { UserId = userId, Reason = reason });
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to kick {userId} from room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to kick {userId} from room {RoomId} - got status: {res.StatusCode}");
- }
- }
- public async Task BanAsync(string userId, string? reason = null) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban",
+ public async Task BanAsync(string userId, string? reason = null) =>
+ await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/ban",
new UserIdAndReason() { UserId = userId, Reason = reason });
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to ban {userId} from room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to ban {userId} from room {RoomId} - got status: {res.StatusCode}");
- }
- }
- public async Task UnbanAsync(string userId) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban",
+ public async Task UnbanAsync(string userId) =>
+ await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/unban",
new UserIdAndReason() { UserId = userId });
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to unban {userId} from room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to unban {userId} from room {RoomId} - got status: {res.StatusCode}");
- }
- }
-
- public async Task<EventIdResponse> SendStateEventAsync(string eventType, object content) {
- var res = await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content);
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine(
- $"Failed to send state event {eventType} to room {RoomId} - got status: {res.StatusCode}");
- throw new Exception(
- $"Failed to send state event {eventType} to room {RoomId} - got status: {res.StatusCode}");
- }
- return await res.Content.ReadFromJsonAsync<EventIdResponse>();
- }
+ public async Task<EventIdResponse> SendStateEventAsync(string eventType, object content) =>
+ await (await _httpClient.PostAsJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/state/{eventType}", content))
+ .Content.ReadFromJsonAsync<EventIdResponse>();
public async Task<EventIdResponse> SendMessageEventAsync(string eventType, MessageEventData content) {
- var url = $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid();
- var res = await _httpClient.PutAsJsonAsync(url, content);
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to send event {eventType} to room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to send event {eventType} to room {RoomId} - got status: {res.StatusCode}");
- }
-
+ var res = await _httpClient.PutAsJsonAsync(
+ $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(), content);
var resu = await res.Content.ReadFromJsonAsync<EventIdResponse>();
-
return resu;
}
public async Task<EventIdResponse> SendFileAsync(string eventType, string fileName, Stream fileStream) {
- var url = $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid();
var content = new MultipartFormDataContent();
content.Add(new StreamContent(fileStream), "file", fileName);
- var res = await _httpClient.PutAsync(url, content);
- if (!res.IsSuccessStatusCode) {
- Console.WriteLine($"Failed to send event {eventType} to room {RoomId} - got status: {res.StatusCode}");
- throw new Exception($"Failed to send event {eventType} to room {RoomId} - got status: {res.StatusCode}");
- }
-
- var resu = await res.Content.ReadFromJsonAsync<EventIdResponse>();
-
- return resu;
+ var res = await
+ (
+ await _httpClient.PutAsync(
+ $"/_matrix/client/v3/rooms/{RoomId}/send/{eventType}/" + Guid.NewGuid(),
+ content
+ )
+ )
+ .Content.ReadFromJsonAsync<EventIdResponse>();
+ return res;
}
public readonly SpaceRoom AsSpace;
diff --git a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
index 6b586c7..3be3130 100644
--- a/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
+++ b/MatrixRoomUtils.Core/RoomTypes/SpaceRoom.cs
@@ -8,20 +8,18 @@ namespace MatrixRoomUtils.Core.RoomTypes;
public class SpaceRoom : GenericRoom {
private readonly AuthenticatedHomeServer _homeServer;
private readonly GenericRoom _room;
+
public SpaceRoom(AuthenticatedHomeServer homeServer, string roomId) : base(homeServer, roomId) {
_homeServer = homeServer;
}
public async Task<List<GenericRoom>> GetRoomsAsync(bool includeRemoved = false) {
var rooms = new List<GenericRoom>();
- var state = await GetStateAsync("");
- if (state != null) {
- var states = state.Value.Deserialize<StateEventResponse[]>()!;
- foreach (var stateEvent in states.Where(x => x.Type == "m.space.child")) {
- var roomId = stateEvent.StateKey;
- if(stateEvent.TypedContent.ToJson() != "{}" || includeRemoved)
- rooms.Add(await _homeServer.GetRoom(roomId));
- }
+ var state = GetFullStateAsync().ToBlockingEnumerable().ToList();
+ var childStates = state.Where(x => x.Type == "m.space.child");
+ foreach (var stateEvent in childStates) {
+ if (stateEvent.TypedContent.ToJson() != "{}" || includeRemoved)
+ rooms.Add(await _homeServer.GetRoom(stateEvent.StateKey));
}
return rooms;
diff --git a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
index 8a22d33..870e0d4 100644
--- a/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
+++ b/MatrixRoomUtils.Core/Services/HomeserverProviderService.cs
@@ -1,7 +1,11 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Responses;
using Microsoft.Extensions.Logging;
+
namespace MatrixRoomUtils.Core.Services;
public class HomeserverProviderService {
@@ -9,7 +13,8 @@ public class HomeserverProviderService {
private readonly ILogger<HomeserverProviderService> _logger;
private readonly HomeserverResolverService _homeserverResolverService;
- public HomeserverProviderService(TieredStorageService tieredStorageService, ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) {
+ public HomeserverProviderService(TieredStorageService tieredStorageService,
+ ILogger<HomeserverProviderService> logger, HomeserverResolverService homeserverResolverService) {
Console.WriteLine("Homeserver provider service instantiated!");
_tieredStorageService = tieredStorageService;
_logger = logger;
@@ -18,9 +23,11 @@ public class HomeserverProviderService {
$"New HomeserverProviderService created with TieredStorageService<{string.Join(", ", tieredStorageService.GetType().GetProperties().Select(x => x.Name))}>!");
}
- public async Task<AuthenticatedHomeServer> GetAuthenticatedWithToken(string homeserver, string accessToken) {
+ public async Task<AuthenticatedHomeServer> GetAuthenticatedWithToken(string homeserver, string accessToken,
+ string? overrideFullDomain = null) {
var hs = new AuthenticatedHomeServer(_tieredStorageService, homeserver, accessToken);
- hs.FullHomeServerDomain = await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
+ hs.FullHomeServerDomain = overrideFullDomain ??
+ await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
hs._httpClient.Dispose();
hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) };
hs._httpClient.Timeout = TimeSpan.FromSeconds(5);
@@ -29,4 +36,48 @@ public class HomeserverProviderService {
hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync<WhoAmIResponse>("/_matrix/client/v3/account/whoami"))!;
return hs;
}
+
+ public async Task<RemoteHomeServer> GetRemoteHomeserver(string homeserver, string? overrideFullDomain = null) {
+ var hs = new RemoteHomeServer(homeserver);
+ hs.FullHomeServerDomain = overrideFullDomain ??
+ await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver);
+ hs._httpClient.Dispose();
+ hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) };
+ hs._httpClient.Timeout = TimeSpan.FromSeconds(5);
+ return hs;
+ }
+
+ public async Task<LoginResponse> Login(string homeserver, string user, string password,
+ string? overrideFullDomain = null) {
+ var hs = await GetRemoteHomeserver(homeserver, overrideFullDomain);
+ var payload = new LoginRequest {
+ Identifier = new() { User = user },
+ Password = password
+ };
+ var resp = await hs._httpClient.PostAsJsonAsync("/_matrix/client/v3/login", payload);
+ var data = await resp.Content.ReadFromJsonAsync<LoginResponse>();
+ return data!;
+ }
+
+ private class LoginRequest {
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = "m.login.password";
+
+ [JsonPropertyName("identifier")]
+ public LoginIdentifier Identifier { get; set; } = new();
+
+ [JsonPropertyName("password")]
+ public string Password { get; set; } = "";
+
+ [JsonPropertyName("initial_device_display_name")]
+ public string InitialDeviceDisplayName { get; set; } = "Rory&::LibMatrix";
+
+ public class LoginIdentifier {
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = "m.id.user";
+
+ [JsonPropertyName("user")]
+ public string User { get; set; } = "";
+ }
+ }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs b/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs
index f6363ab..526a261 100644
--- a/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs
+++ b/MatrixRoomUtils.Core/Services/HomeserverResolverService.cs
@@ -6,11 +6,12 @@ using Microsoft.Extensions.Logging;
namespace MatrixRoomUtils.Core.Services;
public class HomeserverResolverService {
- private readonly HttpClient _httpClient;
+ private readonly MatrixHttpClient _httpClient = new MatrixHttpClient();
private readonly ILogger<HomeserverResolverService> _logger;
- public HomeserverResolverService(HttpClient httpClient, ILogger<HomeserverResolverService> logger) {
- _httpClient = httpClient;
+ private static Dictionary<string, object> _wellKnownCache = new();
+
+ public HomeserverResolverService(ILogger<HomeserverResolverService> logger) {
_logger = logger;
}
@@ -22,6 +23,12 @@ public class HomeserverResolverService {
}
private async Task<string> _resolveHomeserverFromWellKnown(string homeserver) {
+ if(homeserver is null) throw new ArgumentNullException(nameof(homeserver));
+ if (_wellKnownCache.ContainsKey(homeserver)) {
+ if (_wellKnownCache[homeserver] is SemaphoreSlim s) await s.WaitAsync();
+ if (_wellKnownCache[homeserver] is string p) return p;
+ }
+ _wellKnownCache[homeserver] = new SemaphoreSlim(1);
string? result = null;
_logger.LogInformation($"Attempting to resolve homeserver: {homeserver}");
if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
@@ -34,6 +41,7 @@ public class HomeserverResolverService {
if(result is not null) {
_logger.LogInformation($"Resolved homeserver: {homeserver} -> {result}");
+ _wellKnownCache.TryAdd(homeserver, result);
return result;
}
diff --git a/MatrixRoomUtils.Core/Services/ServiceInstaller.cs b/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
index 4a831c1..ef9238d 100644
--- a/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
+++ b/MatrixRoomUtils.Core/Services/ServiceInstaller.cs
@@ -9,14 +9,14 @@ public static class ServiceInstaller {
if (!services.Any(x => x.ServiceType == typeof(TieredStorageService)))
throw new Exception("[MRUCore/DI] No TieredStorageService has been registered!");
//Add config
- if(config != null)
+ if(config is not null)
services.AddSingleton(config);
else {
services.AddSingleton(new RoryLibMatrixConfiguration());
}
//Add services
services.AddScoped<HomeserverProviderService>();
- services.AddScoped<HomeserverResolverService>();
+ services.AddSingleton<HomeserverResolverService>();
services.AddScoped<HttpClient>();
return services;
}
diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index f2c8701..18b4632 100644
--- a/MatrixRoomUtils.Core/StateEvent.cs
+++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -29,7 +29,7 @@ public class StateEvent {
get => _type;
set {
_type = value;
- if (RawContent != null && this is StateEventResponse stateEventResponse) {
+ if (RawContent is not null && this is StateEventResponse stateEventResponse) {
if (File.Exists($"unknown_state_events/{Type}/{stateEventResponse.EventId}.json")) return;
var x = GetType.Name;
}
@@ -46,7 +46,7 @@ public class StateEvent {
get => _rawContent;
set {
_rawContent = value;
- if (Type != null && this is StateEventResponse stateEventResponse) {
+ if (Type is not null && this is StateEventResponse stateEventResponse) {
if (File.Exists($"unknown_state_events/{Type}/{stateEventResponse.EventId}.json")) return;
var x = GetType.Name;
}
diff --git a/MatrixRoomUtils.Core/StateEventTypes/Common/MjolnirShortcodeEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/Common/MjolnirShortcodeEventData.cs
new file mode 100644
index 0000000..efc946d
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/Common/MjolnirShortcodeEventData.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core.StateEventTypes;
+
+[MatrixEvent(EventName = "org.matrix.mjolnir.shortcode")]
+public class MjolnirShortcodeEventData : IStateEventType {
+ [JsonPropertyName("shortcode")]
+ public string? Shortcode { get; set; }
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAliasEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAliasEventData.cs
new file mode 100644
index 0000000..5141ed2
--- /dev/null
+++ b/MatrixRoomUtils.Core/StateEventTypes/Spec/RoomAliasEventData.cs
@@ -0,0 +1,11 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+using MatrixRoomUtils.Core.Interfaces;
+
+namespace MatrixRoomUtils.Core;
+
+[MatrixEvent(EventName = "m.room.alias")]
+public class RoomAliasEventData : IStateEventType {
+ [JsonPropertyName("aliases")]
+ public List<string>? Aliases { get; set; }
+}
\ No newline at end of file
|