diff --git a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
index 368aa20..ee6be72 100644
--- a/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
+++ b/MatrixRoomUtils.Core/AuthenticatedHomeServer.cs
@@ -9,63 +9,47 @@ using MatrixRoomUtils.Core.Responses.Admin;
namespace MatrixRoomUtils.Core;
-public class AuthenticatedHomeServer : IHomeServer
-{
- public string UserId { get; set; }
- public string AccessToken { get; set; }
+public class AuthenticatedHomeServer : IHomeServer {
public readonly HomeserverAdminApi Admin;
- public AuthenticatedHomeServer(string userId, string accessToken, string canonicalHomeServerDomain)
- {
+ public AuthenticatedHomeServer(string userId, string accessToken, string canonicalHomeServerDomain) {
UserId = userId;
AccessToken = accessToken;
HomeServerDomain = canonicalHomeServerDomain;
Admin = new HomeserverAdminApi(this);
- _httpClient = new HttpClient();
+ _httpClient = new MatrixHttpClient();
}
- public async Task<AuthenticatedHomeServer> Configure()
- {
+ public string UserId { get; set; }
+ public string AccessToken { get; set; }
+
+ public async Task<AuthenticatedHomeServer> Configure() {
FullHomeServerDomain = await ResolveHomeserverFromWellKnown(HomeServerDomain);
_httpClient.Dispose();
- _httpClient = new HttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
+ _httpClient = new MatrixHttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
Console.WriteLine("[AHS] Finished setting up http client");
return this;
}
- public async Task<Room> GetRoom(string roomId)
- {
- return new Room(_httpClient, roomId);
- }
+ public async Task<Room> GetRoom(string roomId) => new Room(_httpClient, roomId);
- public async Task<List<Room>> GetJoinedRooms()
- {
+ public async Task<List<Room>> GetJoinedRooms() {
var rooms = new List<Room>();
var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
- if (!roomQuery.IsSuccessStatusCode)
- {
- Console.WriteLine($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
- throw new InvalidDataException($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
- }
var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>();
- foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray())
- {
- rooms.Add(new Room(_httpClient, room.GetString()));
- }
+ foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray()) rooms.Add(new Room(_httpClient, room.GetString()));
Console.WriteLine($"Fetched {rooms.Count} rooms");
return rooms;
}
- public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream")
- {
+ public async Task<string> UploadFile(string fileName, Stream fileStream, string contentType = "application/octet-stream") {
var res = await _httpClient.PostAsync($"/_matrix/media/r0/upload?filename={fileName}", new StreamContent(fileStream));
- if (!res.IsSuccessStatusCode)
- {
+ if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
throw new InvalidDataException($"Failed to upload file: {await res.Content.ReadAsStringAsync()}");
}
@@ -74,11 +58,9 @@ public class AuthenticatedHomeServer : IHomeServer
return resJson.GetProperty("content_uri").GetString()!;
}
- public async Task<Room> CreateRoom(CreateRoomRequest creationEvent)
- {
+ public async Task<Room> CreateRoom(CreateRoomRequest creationEvent) {
var res = await _httpClient.PostAsJsonAsync("/_matrix/client/r0/createRoom", creationEvent);
- if (!res.IsSuccessStatusCode)
- {
+ if (!res.IsSuccessStatusCode) {
Console.WriteLine($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
throw new InvalidDataException($"Failed to create room: {await res.Content.ReadAsStringAsync()}");
}
@@ -86,47 +68,34 @@ public class AuthenticatedHomeServer : IHomeServer
return await GetRoom((await res.Content.ReadFromJsonAsync<JsonObject>())!["room_id"]!.ToString());
}
- public class HomeserverAdminApi
- {
+ public class HomeserverAdminApi {
private readonly AuthenticatedHomeServer _authenticatedHomeServer;
- public HomeserverAdminApi(AuthenticatedHomeServer authenticatedHomeServer)
- {
- _authenticatedHomeServer = authenticatedHomeServer;
- }
+ public HomeserverAdminApi(AuthenticatedHomeServer authenticatedHomeServer) => _authenticatedHomeServer = authenticatedHomeServer;
- public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, string? contentSearch = null)
- {
+ public async IAsyncEnumerable<AdminRoomListingResult.AdminRoomListingResultRoom> SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", string? searchTerm = null, string? contentSearch = null) {
AdminRoomListingResult? res = null;
- int i = 0;
+ var i = 0;
int? totalRooms = null;
- do
- {
+ do {
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 (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}";
+
+ if (res?.NextBatch != null) url += $"&from={res.NextBatch}";
- if (res?.NextBatch != null)
- {
- url += $"&from={res.NextBatch}";
- }
Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---");
res = await _authenticatedHomeServer._httpClient.GetFromJsonAsync<AdminRoomListingResult>(url);
totalRooms ??= res?.TotalRooms;
- Console.WriteLine(res.ToJson(indent:false));
- foreach (var room in res.Rooms)
- {
+ Console.WriteLine(res.ToJson(false));
+ foreach (var room in res.Rooms) {
if (contentSearch != null && !string.IsNullOrEmpty(contentSearch) &&
!(
room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true ||
room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true
)
- )
- {
+ ) {
totalRooms--;
continue;
}
diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
index b4b8d19..0f9eb58 100644
--- a/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
+++ b/MatrixRoomUtils.Core/Authentication/MatrixAuth.cs
@@ -5,35 +5,31 @@ using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core.Authentication;
-public class MatrixAuth
-{
- public static async Task<LoginResponse> Login(string homeserver, string username, string password)
- {
+public class MatrixAuth {
+ public static async Task<LoginResponse> Login(string homeserver, string username, string password) {
Console.WriteLine($"Logging in to {homeserver} as {username}...");
homeserver = (await new RemoteHomeServer(homeserver).Configure()).FullHomeServerDomain;
var hc = new HttpClient();
- var payload = new
- {
+ var payload = new {
type = "m.login.password",
- identifier = new
- {
+ identifier = new {
type = "m.id.user",
user = username
},
- password = password,
+ password,
initial_device_display_name = "Rory&::MatrixRoomUtils"
};
Console.WriteLine($"Sending login request to {homeserver}...");
var resp = await hc.PostAsJsonAsync($"{homeserver}/_matrix/client/r0/login", payload);
Console.WriteLine($"Login: {resp.StatusCode}");
var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
- if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data.ToString());
- if (data.TryGetProperty("retry_after_ms", out var retryAfter))
- {
+ if (!resp.IsSuccessStatusCode) Console.WriteLine("Login: " + data);
+ if (data.TryGetProperty("retry_after_ms", out var retryAfter)) {
Console.WriteLine($"Login: Waiting {retryAfter.GetInt32()}ms before retrying");
await Task.Delay(retryAfter.GetInt32());
return await Login(homeserver, username, password);
}
+
Console.WriteLine($"Login: {data.ToJson()}");
return data.Deserialize<LoginResponse>();
//var token = data.GetProperty("access_token").GetString();
@@ -41,20 +37,16 @@ public class MatrixAuth
}
[Obsolete("Migrate to IHomeServer instance")]
- public static async Task<ProfileResponse> GetProfile(string homeserver, string mxid) =>
- await (await new RemoteHomeServer(homeserver).Configure()).GetProfile(mxid);
+ public static async Task<ProfileResponse> GetProfile(string homeserver, string mxid) => await (await new RemoteHomeServer(homeserver).Configure()).GetProfile(mxid);
- private static async Task<bool> CheckSuccessStatus(string url)
- {
+ private static async Task<bool> CheckSuccessStatus(string url) {
//cors causes failure, try to catch
- try
- {
+ try {
using var hc = new HttpClient();
var resp = await hc.GetAsync(url);
return resp.IsSuccessStatusCode;
}
- catch (Exception e)
- {
+ catch (Exception e) {
Console.WriteLine($"Failed to check success status: {e.Message}");
return false;
}
diff --git a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
index cce71dd..c51baec 100644
--- a/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/DictionaryExtensions.cs
@@ -1,15 +1,13 @@
namespace MatrixRoomUtils.Core.Extensions;
-public static class DictionaryExtensions
-{
- public static bool ChangeKey<TKey, TValue>(this IDictionary<TKey, TValue> dict,
- TKey oldKey, TKey newKey)
- {
+public static class DictionaryExtensions {
+ public static bool ChangeKey<TKey, TValue>(this IDictionary<TKey, TValue> dict,
+ TKey oldKey, TKey newKey) {
TValue value;
if (!dict.Remove(oldKey, out value))
return false;
- dict[newKey] = value; // or dict.Add(newKey, value) depending on ur comfort
+ dict[newKey] = value; // or dict.Add(newKey, value) depending on ur comfort
return true;
}
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
index 8eb0226..47b3121 100644
--- a/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/HttpClientExtensions.cs
@@ -1,19 +1,40 @@
+using System.Text.Json;
+
namespace MatrixRoomUtils.Core.Extensions;
-public static class HttpClientExtensions
-{
- public static async Task<bool> CheckSuccessStatus(this HttpClient hc, string url)
- {
+public static class HttpClientExtensions {
+ public static async Task<bool> CheckSuccessStatus(this HttpClient hc, string url) {
//cors causes failure, try to catch
- try
- {
+ try {
var resp = await hc.GetAsync(url);
return resp.IsSuccessStatusCode;
}
- catch (Exception e)
- {
+ catch (Exception e) {
Console.WriteLine($"Failed to check success status: {e.Message}");
return false;
}
}
+}
+
+public class MatrixHttpClient : HttpClient {
+ public override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
+ 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) {
+ await Task.Delay(ex.RetryAfterMs.Value, cancellationToken);
+ 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/Extensions/JsonElementExtensions.cs b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
index 725c832..b007136 100644
--- a/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/JsonElementExtensions.cs
@@ -5,18 +5,16 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core.Extensions;
-public static class JsonElementExtensions
-{
- public static void FindExtraJsonFields([DisallowNull] this JsonElement? res, Type t)
- {
+public static class JsonElementExtensions {
+ public static void FindExtraJsonFields([DisallowNull] this JsonElement? res, Type t) {
var props = t.GetProperties();
var unknownPropertyFound = false;
- foreach (var field in res.Value.EnumerateObject())
- {
+ foreach (var field in res.Value.EnumerateObject()) {
if (props.Any(x => x.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name == field.Name)) continue;
Console.WriteLine($"[!!] Unknown property {field.Name} in {t.Name}!");
unknownPropertyFound = true;
}
- if(unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
+
+ if (unknownPropertyFound) Console.WriteLine(res.Value.ToJson());
}
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
index 5aa9645..812c81c 100644
--- a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs
@@ -1,15 +1,14 @@
+using System.Text.Encodings.Web;
using System.Text.Json;
namespace MatrixRoomUtils.Core.Extensions;
-public static class ObjectExtensions
-{
- public static string ToJson(this object obj, bool indent = true, bool ignoreNull = false, bool unsafeContent = false)
- {
+public static class ObjectExtensions {
+ public static string ToJson(this object obj, bool indent = true, bool ignoreNull = false, bool unsafeContent = false) {
var jso = new JsonSerializerOptions();
- if(indent) jso.WriteIndented = true;
- if(ignoreNull) jso.IgnoreNullValues = true;
- if(unsafeContent) jso.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
+ if (indent) jso.WriteIndented = true;
+ if (ignoreNull) jso.IgnoreNullValues = true;
+ if (unsafeContent) jso.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
return JsonSerializer.Serialize(obj, jso);
}
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Extensions/StringExtensions.cs b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
index 8fadc6d..b81d59f 100644
--- a/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
+++ b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs
@@ -1,7 +1,6 @@
namespace MatrixRoomUtils.Core.Extensions;
-public static class StringExtensions
-{
+public static class StringExtensions {
// public static async Task<string> GetMediaUrl(this string MxcUrl)
// {
// //MxcUrl: mxc://rory.gay/ocRVanZoUTCcifcVNwXgbtTg
@@ -11,5 +10,4 @@ public static class StringExtensions
// var mediaId = MxcUrl.Split('/')[3];
// return $"{(await new RemoteHomeServer(server).Configure()).FullHomeServerDomain}/_matrix/media/v3/download/{server}/{mediaId}";
// }
-
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
index 3ae1355..9a9ba7a 100644
--- a/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
+++ b/MatrixRoomUtils.Core/Interfaces/IHomeServer.cs
@@ -5,35 +5,32 @@ using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core.Interfaces;
-public class IHomeServer
-{
- private Dictionary<string, ProfileResponse?> _profileCache = new();
+public class IHomeServer {
+ private readonly Dictionary<string, ProfileResponse?> _profileCache = new();
public string HomeServerDomain { get; set; }
public string FullHomeServerDomain { get; set; }
- private protected HttpClient _httpClient { get; set; } = new();
+ private protected MatrixHttpClient _httpClient { get; set; } = new();
- public async Task<string> ResolveHomeserverFromWellKnown(string homeserver)
- {
+ public async Task<string> ResolveHomeserverFromWellKnown(string homeserver) {
var res = await _resolveHomeserverFromWellKnown(homeserver);
- if(!res.StartsWith("http")) res = "https://" + res;
- if(res.EndsWith(":443")) res = res.Substring(0, res.Length - 4);
+ if (!res.StartsWith("http")) res = "https://" + res;
+ if (res.EndsWith(":443")) res = res.Substring(0, res.Length - 4);
return res;
}
- private async Task<string> _resolveHomeserverFromWellKnown(string homeserver)
- {
- if (RuntimeCache.HomeserverResolutionCache.Count == 0)
- {
+
+ private async Task<string> _resolveHomeserverFromWellKnown(string homeserver) {
+ if (RuntimeCache.HomeserverResolutionCache.Count == 0) {
Console.WriteLine("No cached homeservers, resolving...");
await Task.Delay(Random.Shared.Next(1000, 5000));
- }
- if (RuntimeCache.HomeserverResolutionCache.ContainsKey(homeserver))
- {
- if (RuntimeCache.HomeserverResolutionCache[homeserver].ResolutionTime < DateTime.Now.AddHours(1))
- {
+ }
+
+ if (RuntimeCache.HomeserverResolutionCache.ContainsKey(homeserver)) {
+ if (RuntimeCache.HomeserverResolutionCache[homeserver].ResolutionTime < DateTime.Now.AddHours(1)) {
Console.WriteLine($"Found cached homeserver: {RuntimeCache.HomeserverResolutionCache[homeserver].Result}");
return RuntimeCache.HomeserverResolutionCache[homeserver].Result;
}
+
Console.WriteLine($"Cached homeserver expired, removing: {RuntimeCache.HomeserverResolutionCache[homeserver].Result}");
RuntimeCache.HomeserverResolutionCache.Remove(homeserver);
}
@@ -42,29 +39,25 @@ public class IHomeServer
string result = null;
Console.WriteLine($"Resolving homeserver: {homeserver}");
if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver;
- if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client"))
- {
- Console.WriteLine($"Got successful response for client well-known...");
+ if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) {
+ Console.WriteLine("Got successful response for client well-known...");
var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/client");
Console.WriteLine($"Response: {resp.ToString()}");
var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString();
result = hs;
}
- else
- {
- Console.WriteLine($"No client well-known...");
- if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server"))
- {
+ else {
+ Console.WriteLine("No client well-known...");
+ if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) {
var resp = await _httpClient.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server");
var hs = resp.GetProperty("m.server").GetString();
result = hs;
}
- else
- {
- Console.WriteLine($"No server well-known...");
- if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) result = homeserver;
- else
- {
+ else {
+ Console.WriteLine("No server well-known...");
+ if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions"))
+ result = homeserver;
+ else {
Console.WriteLine("No homeserver on shortname...");
if (await _httpClient.CheckSuccessStatus($"{homeserver.Replace("//", "//matrix.")}/_matrix/client/versions")) result = homeserver.Replace("//", "//matrix.");
else Console.WriteLine($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!");
@@ -72,30 +65,27 @@ public class IHomeServer
}
}
- if (result != null)
- {
+ if (result != null) {
Console.WriteLine($"Resolved homeserver: {homeserver} -> {result}");
- RuntimeCache.HomeserverResolutionCache.TryAdd(homeserver, new()
- {
+ RuntimeCache.HomeserverResolutionCache.TryAdd(homeserver, new HomeServerResolutionResult {
Result = result,
ResolutionTime = DateTime.Now
});
return result;
}
+
throw new InvalidDataException($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!");
}
- 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)
- {
+
+ 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];
}
}
@@ -103,13 +93,11 @@ public class IHomeServer
_profileCache.Add(mxid, null);
var resp = await _httpClient.GetAsync($"/_matrix/client/r0/profile/{mxid}");
var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
- if(!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data.ToString());
+ if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
var profile = data.Deserialize<ProfileResponse>();
_profileCache[mxid] = profile;
return profile;
}
- public string? ResolveMediaUri(string mxc)
- {
- return mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/r0/download/");
- }
+
+ public string? ResolveMediaUri(string mxc) => mxc.Replace("mxc://", $"{FullHomeServerDomain}/_matrix/media/r0/download/");
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/MatrixException.cs b/MatrixRoomUtils.Core/MatrixException.cs
new file mode 100644
index 0000000..3df70e1
--- /dev/null
+++ b/MatrixRoomUtils.Core/MatrixException.cs
@@ -0,0 +1,57 @@
+using System.Text.Json.Serialization;
+using MatrixRoomUtils.Core.Extensions;
+
+namespace MatrixRoomUtils.Core;
+
+public class MatrixException : Exception {
+ [JsonPropertyName("errcode")]
+ public string ErrorCode { get; set; }
+
+ [JsonPropertyName("error")]
+ public string Error { get; set; }
+
+ [JsonPropertyName("soft_logout")]
+ public bool? SoftLogout { get; set; }
+
+ [JsonPropertyName("retry_after_ms")]
+ public int? RetryAfterMs { get; set; }
+
+ public override string Message =>
+ ErrorCode switch {
+ // common
+ "M_FORBIDDEN" => "You do not have permission to perform this action: " + Error,
+ "M_UNKNOWN_TOKEN" => "The access token specified was not recognised: " + Error + (SoftLogout == true ? " (soft logout)" : ""),
+ "M_MISSING_TOKEN" => "No access token was specified: " + Error,
+ "M_BAD_JSON" => "Request contained valid JSON, but it was malformed in some way: " + Error,
+ "M_NOT_JSON" => "Request did not contain valid JSON: " + Error,
+ "M_NOT_FOUND" => "The requested resource was not found: " + Error,
+ "M_LIMIT_EXCEEDED" => "Too many requests have been sent in a short period of time. Wait a while then try again: " + Error,
+ "M_UNRECOGNISED" => "The server did not recognise the request: " + Error,
+ "M_UNKOWN" => "The server encountered an unexpected error: " + Error,
+ // endpoint specific
+ "M_UNAUTHORIZED" => "The request did not contain valid authentication information for the target of the request: " + Error,
+ "M_USER_DEACTIVATED" => "The user ID associated with the request has been deactivated: " + Error,
+ "M_USER_IN_USE" => "The user ID associated with the request is already in use: " + Error,
+ "M_INVALID_USERNAME" => "The requested user ID is not valid: " + Error,
+ "M_ROOM_IN_USE" => "The room alias requested is already taken: " + Error,
+ "M_INVALID_ROOM_STATE" => "The room associated with the request is not in a valid state to perform the request: " + Error,
+ "M_THREEPID_IN_USE" => "The threepid requested is already associated with a user ID on this server: " + Error,
+ "M_THREEPID_NOT_FOUND" => "The threepid requested is not associated with any user ID: " + Error,
+ "M_THREEPID_AUTH_FAILED" => "The provided threepid and/or token was invalid: " + Error,
+ "M_THREEPID_DENIED" => "The homeserver does not permit the third party identifier in question: " + Error,
+ "M_SERVER_NOT_TRUSTED" => "The homeserver does not trust the identity server: " + Error,
+ "M_UNSUPPORTED_ROOM_VERSION" => "The room version is not supported: " + Error,
+ "M_INCOMPATIBLE_ROOM_VERSION" => "The room version is incompatible: " + Error,
+ "M_BAD_STATE" => "The request was invalid because the state was invalid: " + Error,
+ "M_GUEST_ACCESS_FORBIDDEN" => "Guest access is forbidden: " + Error,
+ "M_CAPTCHA_NEEDED" => "Captcha needed: " + Error,
+ "M_CAPTCHA_INVALID" => "Captcha invalid: " + Error,
+ "M_MISSING_PARAM" => "Missing parameter: " + Error,
+ "M_INVALID_PARAM" => "Invalid parameter: " + Error,
+ "M_TOO_LARGE" => "The request or entity was too large: " + Error,
+ "M_EXCLUSIVE" => "The resource being requested is reserved by an application service, or the application service making the request has not created the resource: " + Error,
+ "M_RESOURCE_LIMIT_EXCEEDED" => "Exceeded resource limit: " + Error,
+ "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" => "Cannot leave server notice room: " + Error,
+ _ => "Unknown error: " + new { ErrorCode, Error, SoftLogout, RetryAfterMs }.ToJson()
+ };
+}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RatelimitedHttpClient.cs b/MatrixRoomUtils.Core/RatelimitedHttpClient.cs
deleted file mode 100644
index 61eab07..0000000
--- a/MatrixRoomUtils.Core/RatelimitedHttpClient.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace MatrixRoomUtils.Core;
-
-public class RatelimitedHttpClient : HttpClient
-{
-
-
-}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RemoteHomeServer.cs b/MatrixRoomUtils.Core/RemoteHomeServer.cs
index 942f873..3f50d2e 100644
--- a/MatrixRoomUtils.Core/RemoteHomeServer.cs
+++ b/MatrixRoomUtils.Core/RemoteHomeServer.cs
@@ -1,50 +1,39 @@
using System.Net.Http.Json;
using System.Text.Json;
+using MatrixRoomUtils.Core.Extensions;
using MatrixRoomUtils.Core.Interfaces;
namespace MatrixRoomUtils.Core;
-public class RemoteHomeServer : IHomeServer
-{
-
-
- public RemoteHomeServer(string canonicalHomeServerDomain)
- {
+public class RemoteHomeServer : IHomeServer {
+ public RemoteHomeServer(string canonicalHomeServerDomain) {
HomeServerDomain = canonicalHomeServerDomain;
- _httpClient = new HttpClient();
+ _httpClient = new MatrixHttpClient();
_httpClient.Timeout = TimeSpan.FromSeconds(5);
}
- public async Task<RemoteHomeServer> Configure()
- {
+
+ public async Task<RemoteHomeServer> Configure() {
FullHomeServerDomain = await ResolveHomeserverFromWellKnown(HomeServerDomain);
_httpClient.Dispose();
- _httpClient = new HttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
+ _httpClient = new MatrixHttpClient { BaseAddress = new Uri(FullHomeServerDomain) };
_httpClient.Timeout = TimeSpan.FromSeconds(5);
Console.WriteLine("[RHS] Finished setting up http client");
return this;
}
-
- public async Task<Room> GetRoom(string roomId)
- {
- return new Room(_httpClient, roomId);
- }
- public async Task<List<Room>> GetJoinedRooms()
- {
+ public async Task<Room> GetRoom(string roomId) => new Room(_httpClient, roomId);
+
+ public async Task<List<Room>> GetJoinedRooms() {
var rooms = new List<Room>();
var roomQuery = await _httpClient.GetAsync("/_matrix/client/v3/joined_rooms");
- if (!roomQuery.IsSuccessStatusCode)
- {
+ if (!roomQuery.IsSuccessStatusCode) {
Console.WriteLine($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
throw new InvalidDataException($"Failed to get rooms: {await roomQuery.Content.ReadAsStringAsync()}");
}
var roomsJson = await roomQuery.Content.ReadFromJsonAsync<JsonElement>();
- foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray())
- {
- rooms.Add(new Room(_httpClient, room.GetString()));
- }
+ foreach (var room in roomsJson.GetProperty("joined_rooms").EnumerateArray()) rooms.Add(new Room(_httpClient, room.GetString()));
return rooms;
}
diff --git a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
index 8ec0e4f..37bb3ba 100644
--- a/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
+++ b/MatrixRoomUtils.Core/Responses/Admin/AdminRoomListingResult.cs
@@ -2,8 +2,7 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core.Responses.Admin;
-public class AdminRoomListingResult
-{
+public class AdminRoomListingResult {
[JsonPropertyName("offset")]
public int Offset { get; set; }
@@ -19,8 +18,7 @@ public class AdminRoomListingResult
[JsonPropertyName("rooms")]
public List<AdminRoomListingResultRoom> Rooms { get; set; } = new();
- public class AdminRoomListingResultRoom
- {
+ public class AdminRoomListingResultRoom {
[JsonPropertyName("room_id")]
public string RoomId { get; set; }
diff --git a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
index 5df99f7..da7d569 100644
--- a/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
+++ b/MatrixRoomUtils.Core/Responses/CreateRoomRequest.cs
@@ -6,58 +6,56 @@ using MatrixRoomUtils.Core.Extensions;
namespace MatrixRoomUtils.Core.Responses;
-public class CreateRoomRequest
-{
- [JsonPropertyName("name")] public string Name { get; set; } = null!;
+public class CreateRoomRequest {
+ [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
+
+ public CreateRoomRequest() => _creationContentBaseType = new CreationContentBaseType(this);
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = null!;
- [JsonPropertyName("room_alias_name")] public string RoomAliasName { get; set; } = null!;
+ [JsonPropertyName("room_alias_name")]
+ public string RoomAliasName { get; set; } = null!;
//we dont want to use this, we want more control
// [JsonPropertyName("preset")]
// public string Preset { get; set; } = null!;
- [JsonPropertyName("initial_state")] public List<StateEvent> InitialState { get; set; } = null!;
- [JsonPropertyName("visibility")] public string Visibility { get; set; } = null!;
+ [JsonPropertyName("initial_state")]
+ public List<StateEvent> InitialState { get; set; } = null!;
+
+ [JsonPropertyName("visibility")]
+ public string Visibility { get; set; } = null!;
[JsonPropertyName("power_level_content_override")]
public PowerLevelEvent PowerLevelContentOverride { get; set; } = null!;
- [JsonPropertyName("creation_content")] public JsonObject CreationContent { get; set; } = new();
+ [JsonPropertyName("creation_content")]
+ public JsonObject CreationContent { get; set; } = new();
/// <summary>
- /// For use only when you can't use the CreationContent property
+ /// For use only when you can't use the CreationContent property
/// </summary>
- public StateEvent this[string event_type, string event_key = ""]
- {
+ public StateEvent this[string event_type, string event_key = ""] {
get => InitialState.First(x => x.Type == event_type && x.StateKey == event_key);
- set
- {
+ set {
var stateEvent = InitialState.FirstOrDefault(x => x.Type == event_type && x.StateKey == event_key);
if (stateEvent == null)
- {
InitialState.Add(value);
- }
else
- {
InitialState[InitialState.IndexOf(stateEvent)] = value;
- }
}
}
//extra properties
[JsonIgnore]
- public string HistoryVisibility
- {
- get
- {
+ public string HistoryVisibility {
+ get {
var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.history_visibility");
- if (stateEvent == null)
- {
- InitialState.Add(new StateEvent()
- {
+ if (stateEvent == null) {
+ InitialState.Add(new StateEvent {
Type = "m.room.history_visibility",
- Content = new JsonObject()
- {
+ Content = new JsonObject {
["history_visibility"] = "shared"
}
});
@@ -66,22 +64,16 @@ public class CreateRoomRequest
return stateEvent.ContentAsJsonNode["history_visibility"].GetValue<string>();
}
- set
- {
+ set {
var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.history_visibility");
if (stateEvent == null)
- {
- InitialState.Add(new StateEvent()
- {
+ InitialState.Add(new StateEvent {
Type = "m.room.history_visibility",
- Content = new JsonObject()
- {
+ Content = new JsonObject {
["history_visibility"] = value
}
});
- }
- else
- {
+ else {
var v = stateEvent.ContentAsJsonNode;
v["history_visibility"] = value;
stateEvent.ContentAsJsonNode = v;
@@ -90,18 +82,13 @@ public class CreateRoomRequest
}
[JsonIgnore]
- public string RoomIcon
- {
- get
- {
+ public string RoomIcon {
+ get {
var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.avatar");
- if (stateEvent == null)
- {
- InitialState.Add(new StateEvent()
- {
+ if (stateEvent == null) {
+ InitialState.Add(new StateEvent {
Type = "m.room.avatar",
- Content = new JsonObject()
- {
+ Content = new JsonObject {
["url"] = ""
}
});
@@ -110,22 +97,16 @@ public class CreateRoomRequest
return stateEvent.ContentAsJsonNode["url"].GetValue<string>();
}
- set
- {
+ set {
var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.avatar");
if (stateEvent == null)
- {
- InitialState.Add(new StateEvent()
- {
+ InitialState.Add(new StateEvent {
Type = "m.room.avatar",
- Content = new JsonObject()
- {
+ Content = new JsonObject {
["url"] = value
}
});
- }
- else
- {
+ else {
var v = stateEvent.ContentAsJsonNode;
v["url"] = value;
stateEvent.ContentAsJsonNode = v;
@@ -177,64 +158,50 @@ public class CreateRoomRequest
// }
// }
- public ServerACL ServerACLs
- {
- get
- {
+ public ServerACL ServerACLs {
+ get {
var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
- if (stateEvent == null)
- {
- InitialState.Add(new StateEvent()
- {
+ if (stateEvent == null) {
+ InitialState.Add(new StateEvent {
Type = "m.room.server_acl",
- Content = new JsonObject()
- {
- ["allow"] = new JsonArray()
- {
+ Content = new JsonObject {
+ ["allow"] = new JsonArray {
"*"
},
["deny"] = new JsonArray()
}
});
- return new ServerACL()
- {
- Allow = new List<string>()
- {
+ return new ServerACL {
+ Allow = new List<string> {
"*"
},
Deny = new List<string>(),
AllowIpLiterals = true
};
}
- return new ServerACL()
- {
- Allow = JsonSerializer.Deserialize<List<string>>(stateEvent.ContentAsJsonNode["allow"]),
- Deny = JsonSerializer.Deserialize<List<string>>(stateEvent.ContentAsJsonNode["deny"]),
+
+ return new ServerACL {
+ Allow = stateEvent.ContentAsJsonNode["allow"].Deserialize<List<string>>(),
+ Deny = stateEvent.ContentAsJsonNode["deny"].Deserialize<List<string>>(),
AllowIpLiterals = true
};
}
- set
- {
+ set {
Console.WriteLine($"Setting server acl to {value.ToJson()}");
var stateEvent = InitialState.FirstOrDefault(x => x.Type == "m.room.server_acl");
if (stateEvent == null)
- {
- InitialState.Add(new StateEvent()
- {
+ InitialState.Add(new StateEvent {
Type = "m.room.server_acl",
- Content = new JsonObject()
- {
- ["allow"] = JsonArray.Parse(JsonSerializer.Serialize(value.Allow)),
- ["deny"] = JsonArray.Parse(JsonSerializer.Serialize(value.Deny))
- ["allow_ip_literals"] = value.AllowIpLiterals
+ Content = new JsonObject {
+ ["allow"] = JsonNode.Parse(JsonSerializer.Serialize(value.Allow)),
+ ["deny"] = JsonNode.Parse(JsonSerializer.Serialize(value.Deny))
+ ["allow_ip_literals"] = value.AllowIpLiterals
}
});
- }
- else
- {
+ else {
var v = stateEvent.ContentAsJsonNode;
- v["allow"] = JsonArray.Parse(JsonSerializer.Serialize(value.Allow));
- v["deny"] = JsonArray.Parse(JsonSerializer.Serialize(value.Deny));
+ v["allow"] = JsonNode.Parse(JsonSerializer.Serialize(value.Allow));
+ v["deny"] = JsonNode.Parse(JsonSerializer.Serialize(value.Deny));
v["allow_ip_literals"] = value.AllowIpLiterals;
stateEvent.ContentAsJsonNode = v;
Console.WriteLine($"v={v.ToJson()}");
@@ -243,14 +210,7 @@ public class CreateRoomRequest
}
}
-
- [JsonIgnore] public CreationContentBaseType _creationContentBaseType;
-
- public CreateRoomRequest() => _creationContentBaseType = new(this);
-
-
- public Dictionary<string, string> Validate()
- {
+ public Dictionary<string, string> Validate() {
Dictionary<string, string> errors = new();
if (!Regex.IsMatch(RoomAliasName, @"[a-zA-Z0-9_\-]+$"))
errors.Add("room_alias_name", "Room alias name must only contain letters, numbers, underscores, and hyphens.");
@@ -259,49 +219,65 @@ public class CreateRoomRequest
}
}
-public class CreationContentBaseType
-{
+public class CreationContentBaseType {
private readonly CreateRoomRequest createRoomRequest;
- public CreationContentBaseType(CreateRoomRequest createRoomRequest)
- {
- this.createRoomRequest = createRoomRequest;
- }
+ public CreationContentBaseType(CreateRoomRequest createRoomRequest) => this.createRoomRequest = createRoomRequest;
[JsonPropertyName("type")]
- public string Type
- {
+ public string Type {
get => (string)createRoomRequest.CreationContent["type"];
- set
- {
+ set {
if (value is "null" or "") createRoomRequest.CreationContent.Remove("type");
else createRoomRequest.CreationContent["type"] = value;
}
}
}
-public class PowerLevelEvent
-{
- [JsonPropertyName("ban")] public int Ban { get; set; } // = 50;
- [JsonPropertyName("events_default")] public int EventsDefault { get; set; } // = 0;
- [JsonPropertyName("events")] public Dictionary<string, int> Events { get; set; } // = null!;
- [JsonPropertyName("invite")] public int Invite { get; set; } // = 50;
- [JsonPropertyName("kick")] public int Kick { get; set; } // = 50;
- [JsonPropertyName("notifications")] public NotificationsPL NotificationsPl { get; set; } // = null!;
- [JsonPropertyName("redact")] public int Redact { get; set; } // = 50;
- [JsonPropertyName("state_default")] public int StateDefault { get; set; } // = 50;
- [JsonPropertyName("users")] public Dictionary<string, int> Users { get; set; } // = null!;
- [JsonPropertyName("users_default")] public int UsersDefault { get; set; } // = 0;
+public class PowerLevelEvent {
+ [JsonPropertyName("ban")]
+ public int Ban { get; set; } // = 50;
+
+ [JsonPropertyName("events_default")]
+ public int EventsDefault { get; set; } // = 0;
+
+ [JsonPropertyName("events")]
+ public Dictionary<string, int> Events { get; set; } // = null!;
+
+ [JsonPropertyName("invite")]
+ public int Invite { get; set; } // = 50;
+
+ [JsonPropertyName("kick")]
+ public int Kick { get; set; } // = 50;
+
+ [JsonPropertyName("notifications")]
+ public NotificationsPL NotificationsPl { get; set; } // = null!;
+
+ [JsonPropertyName("redact")]
+ public int Redact { get; set; } // = 50;
+
+ [JsonPropertyName("state_default")]
+ public int StateDefault { get; set; } // = 50;
+
+ [JsonPropertyName("users")]
+ public Dictionary<string, int> Users { get; set; } // = null!;
+
+ [JsonPropertyName("users_default")]
+ public int UsersDefault { get; set; } // = 0;
}
-public class NotificationsPL
-{
- [JsonPropertyName("room")] public int Room { get; set; } = 50;
+public class NotificationsPL {
+ [JsonPropertyName("room")]
+ public int Room { get; set; } = 50;
}
-public class ServerACL
-{
- [JsonPropertyName("allow")] public List<string> Allow { get; set; } // = null!;
- [JsonPropertyName("deny")] public List<string> Deny { get; set; } // = null!;
- [JsonPropertyName("allow_ip_literals")] public bool AllowIpLiterals { get; set; } // = false;
+public class ServerACL {
+ [JsonPropertyName("allow")]
+ public List<string> Allow { get; set; } // = null!;
+
+ [JsonPropertyName("deny")]
+ public List<string> Deny { get; set; } // = null!;
+
+ [JsonPropertyName("allow_ip_literals")]
+ public bool AllowIpLiterals { get; set; } // = false;
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/LoginResponse.cs b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
index 34b42d1..3259e44 100644
--- a/MatrixRoomUtils.Core/Responses/LoginResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/LoginResponse.cs
@@ -4,27 +4,26 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core.Responses;
-public class LoginResponse
-{
+public class LoginResponse {
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
+
[JsonPropertyName("device_id")]
public string DeviceId { get; set; }
+
[JsonPropertyName("home_server")]
public string HomeServer { get; set; }
+
[JsonPropertyName("user_id")]
public string UserId { get; set; }
-
- public async Task<ProfileResponse> GetProfile()
- {
+
+ public async Task<ProfileResponse> GetProfile() {
var hc = new HttpClient();
var resp = await hc.GetAsync($"{HomeServer}/_matrix/client/r0/profile/{UserId}");
var data = await resp.Content.ReadFromJsonAsync<JsonElement>();
- if(!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data.ToString());
+ if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data);
return data.Deserialize<ProfileResponse>();
}
- public async Task<string> GetCanonicalHomeserverUrl()
- {
- return (await new RemoteHomeServer(HomeServer).Configure()).FullHomeServerDomain;
- }
+
+ public async Task<string> GetCanonicalHomeserverUrl() => (await new RemoteHomeServer(HomeServer).Configure()).FullHomeServerDomain;
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs b/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
index 2c0b679..db72386 100644
--- a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/ProfileResponse.cs
@@ -2,10 +2,10 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core.Responses;
-public class ProfileResponse
-{
+public class ProfileResponse {
[JsonPropertyName("avatar_url")]
public string? AvatarUrl { get; set; } = "";
+
[JsonPropertyName("displayname")]
public string? DisplayName { get; set; } = "";
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
index 670c121..36f0a36 100644
--- a/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
+++ b/MatrixRoomUtils.Core/Responses/StateEventResponse.cs
@@ -2,43 +2,47 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core;
-public class StateEventResponse : StateEvent
-{
+public class StateEventResponse : StateEvent {
[JsonPropertyName("origin_server_ts")]
public ulong OriginServerTs { get; set; }
+
[JsonPropertyName("room_id")]
public string RoomId { get; set; }
+
[JsonPropertyName("sender")]
public string Sender { get; set; }
+
[JsonPropertyName("unsigned")]
public UnsignedData? Unsigned { get; set; }
+
[JsonPropertyName("event_id")]
public string EventId { get; set; }
+
[JsonPropertyName("user_id")]
public string UserId { get; set; }
+
[JsonPropertyName("replaces_state")]
public string ReplacesState { get; set; }
+
[JsonPropertyName("prev_content")]
public dynamic PrevContent { get; set; }
-
-
- public class UnsignedData
- {
+
+ public class UnsignedData {
[JsonPropertyName("age")]
public ulong Age { get; set; }
+
[JsonPropertyName("prev_content")]
public dynamic? PrevContent { get; set; }
+
[JsonPropertyName("redacted_because")]
public dynamic? RedactedBecause { get; set; }
+
[JsonPropertyName("transaction_id")]
public string? TransactionId { get; set; }
-
}
}
-public class StateEventResponse<T> : StateEventResponse where T : class
-{
-
+public class StateEventResponse<T> : StateEventResponse where T : class {
[JsonPropertyName("content")]
public T Content { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/Room.cs b/MatrixRoomUtils.Core/Room.cs
index f228271..a867c0c 100644
--- a/MatrixRoomUtils.Core/Room.cs
+++ b/MatrixRoomUtils.Core/Room.cs
@@ -1,6 +1,4 @@
-using System.Diagnostics.CodeAnalysis;
using System.Net.Http.Json;
-using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Web;
@@ -8,26 +6,23 @@ using MatrixRoomUtils.Core.Extensions;
namespace MatrixRoomUtils.Core;
-public class Room
-{
+public class Room {
private readonly HttpClient _httpClient;
- public string RoomId { get; set; }
- public Room(HttpClient httpClient, string roomId)
- {
+ public Room(HttpClient httpClient, string roomId) {
_httpClient = httpClient;
RoomId = roomId;
}
- public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "", bool logOnFailure = true)
- {
+ public string RoomId { get; set; }
+
+ public async Task<JsonElement?> GetStateAsync(string type, string stateKey = "", bool logOnFailure = true) {
var url = $"/_matrix/client/v3/rooms/{RoomId}/state";
if (!string.IsNullOrEmpty(type)) url += $"/{type}";
if (!string.IsNullOrEmpty(stateKey)) url += $"/{stateKey}";
var res = await _httpClient.GetAsync(url);
- if (!res.IsSuccessStatusCode)
- {
+ if (!res.IsSuccessStatusCode) {
if (logOnFailure) Console.WriteLine($"{RoomId}/{stateKey}/{type} - got status: {res.StatusCode}");
return null;
}
@@ -36,20 +31,17 @@ public class Room
return result;
}
- public async Task<T?> GetStateAsync<T>(string type, string stateKey = "", bool logOnFailure = false)
- {
+ 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<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b", string filter = "")
- {
+
+ public async Task<MessagesResponse> GetMessagesAsync(string from = "", int limit = 10, string dir = "b", string filter = "") {
var url = $"/_matrix/client/r0/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)
- {
+ 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}");
}
@@ -58,11 +50,9 @@ public class Room
return result ?? new MessagesResponse();
}
- public async Task<string> GetNameAsync()
- {
+ public async Task<string> GetNameAsync() {
var res = await GetStateAsync("m.room.name");
- if (!res.HasValue)
- {
+ if (!res.HasValue) {
Console.WriteLine($"Room {RoomId} has no name!");
return RoomId;
}
@@ -72,22 +62,19 @@ public class Room
return resn;
}
- public async Task JoinAsync(string[]? homeservers = null)
- {
- string join_url = $"/_matrix/client/r0/join/{HttpUtility.UrlEncode(RoomId)}";
+ public async Task JoinAsync(string[]? homeservers = null) {
+ var join_url = $"/_matrix/client/r0/join/{HttpUtility.UrlEncode(RoomId)}";
Console.WriteLine($"Calling {join_url} with {homeservers?.Length ?? 0} via's...");
if (homeservers == null || homeservers.Length == 0) homeservers = new[] { RoomId.Split(':')[1] };
var fullJoinUrl = $"{join_url}?server_name=" + string.Join("&server_name=", homeservers);
var res = await _httpClient.PostAsync(fullJoinUrl, null);
}
- public async Task<List<string>> GetMembersAsync(bool joinedOnly = true)
- {
+ 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())
- {
+ 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();
@@ -97,63 +84,52 @@ public class Room
return members;
}
- public async Task<List<string>> GetAliasesAsync()
- {
+ 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() ?? "");
- }
+ foreach (var alias in res.Value.GetProperty("aliases").EnumerateArray()) aliases.Add(alias.GetString() ?? "");
return aliases;
}
- public async Task<string> GetCanonicalAliasAsync()
- {
+ 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()
- {
+ 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()
- {
+ 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<JoinRules> GetJoinRuleAsync()
- {
+ public async Task<JoinRules> GetJoinRuleAsync() {
var res = await GetStateAsync("m.room.join_rules");
if (!res.HasValue) return new JoinRules();
return res.Value.Deserialize<JoinRules>() ?? new JoinRules();
}
- public async Task<string> GetHistoryVisibilityAsync()
- {
+ 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<string> GetGuestAccessAsync()
- {
+ 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<CreateEvent> GetCreateEventAsync()
- {
+ public async Task<CreateEvent> GetCreateEventAsync() {
var res = await GetStateAsync("m.room.create");
if (!res.HasValue) return new CreateEvent();
@@ -163,33 +139,45 @@ public class Room
}
}
-public class MessagesResponse
-{
+public class MessagesResponse {
[JsonPropertyName("start")]
public string Start { get; set; }
+
[JsonPropertyName("end")]
public string? End { get; set; }
+
[JsonPropertyName("chunk")]
public List<StateEventResponse> Chunk { get; set; } = new();
+
[JsonPropertyName("state")]
public List<StateEventResponse> State { get; set; } = new();
}
-public class CreateEvent
-{
- [JsonPropertyName("creator")] public string Creator { get; set; }
- [JsonPropertyName("room_version")] public string RoomVersion { get; set; }
- [JsonPropertyName("type")] public string? Type { get; set; }
- [JsonPropertyName("predecessor")] public object? Predecessor { get; set; }
- [JsonPropertyName("m.federate")] public bool Federate { get; set; }
+public class CreateEvent {
+ [JsonPropertyName("creator")]
+ public string Creator { get; set; }
+
+ [JsonPropertyName("room_version")]
+ public string RoomVersion { get; set; }
+
+ [JsonPropertyName("type")]
+ public string? Type { get; set; }
+
+ [JsonPropertyName("predecessor")]
+ public object? Predecessor { get; set; }
+
+ [JsonPropertyName("m.federate")]
+ public bool Federate { get; set; }
}
-public class JoinRules
-{
+public class JoinRules {
private const string Public = "public";
private const string Invite = "invite";
private const string Knock = "knock";
- [JsonPropertyName("join_rule")] public string JoinRule { get; set; }
- [JsonPropertyName("allow")] public List<string> Allow { get; set; }
+ [JsonPropertyName("join_rule")]
+ public string JoinRule { get; set; }
+
+ [JsonPropertyName("allow")]
+ public List<string> Allow { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/RuntimeCache.cs b/MatrixRoomUtils.Core/RuntimeCache.cs
index 4f73341..a2fcf40 100644
--- a/MatrixRoomUtils.Core/RuntimeCache.cs
+++ b/MatrixRoomUtils.Core/RuntimeCache.cs
@@ -3,9 +3,21 @@ using MatrixRoomUtils.Core.Responses;
namespace MatrixRoomUtils.Core;
-public class RuntimeCache
-{
+public class RuntimeCache {
public static bool WasLoaded = false;
+
+ static RuntimeCache() =>
+ Task.Run(async () => {
+ while (true) {
+ await Task.Delay(1000);
+ foreach (var (key, value) in GenericResponseCache)
+ if (value.Cache.Any())
+ SaveObject("rory.matrixroomutils.generic_cache:" + key, value);
+ else
+ RemoveObject("rory.matrixroomutils.generic_cache:" + key);
+ }
+ });
+
public static string? LastUsedToken { get; set; }
public static AuthenticatedHomeServer CurrentHomeServer { get; set; }
public static Dictionary<string, UserInfo> LoginSessions { get; set; } = new();
@@ -18,110 +30,69 @@ public class RuntimeCache
public static Action Save { get; set; } = () => { Console.WriteLine("RuntimeCache.Save() was called, but no callback was set!"); };
public static Action<string, object> SaveObject { get; set; } = (key, value) => { Console.WriteLine($"RuntimeCache.SaveObject({key}, {value}) was called, but no callback was set!"); };
public static Action<string> RemoveObject { get; set; } = key => { Console.WriteLine($"RuntimeCache.RemoveObject({key}) was called, but no callback was set!"); };
-
- static RuntimeCache()
- {
- Task.Run(async () =>
- {
- while(true)
- {
- await Task.Delay(1000);
- foreach (var (key, value) in GenericResponseCache)
- {
- if (value.Cache.Any())
- SaveObject("rory.matrixroomutils.generic_cache:" + key, value);
- else
- {
- RemoveObject("rory.matrixroomutils.generic_cache:" + key);
- }
- }
- }
- });
- }
}
-public class UserInfo
-{
+public class UserInfo {
public ProfileResponse Profile { get; set; } = new();
public LoginResponse LoginResponse { get; set; }
- public string AccessToken
- {
- get => LoginResponse.AccessToken;
- }
+ public string AccessToken => LoginResponse.AccessToken;
}
-public class HomeServerResolutionResult
-{
+public class HomeServerResolutionResult {
public string Result { get; set; }
public DateTime ResolutionTime { get; set; }
}
-public class ObjectCache<T> where T : class
-{
+public class ObjectCache<T> where T : class {
+ public ObjectCache() =>
+ //expiry timer
+ Task.Run(async () => {
+ while (Cache.Any()) {
+ await Task.Delay(1000);
+ foreach (var x in Cache.Where(x => x.Value.ExpiryTime < DateTime.Now).OrderBy(x => x.Value.ExpiryTime).Take(15).ToList())
+ // Console.WriteLine($"Removing {x.Key} from cache");
+ Cache.Remove(x.Key);
+ //RuntimeCache.SaveObject("rory.matrixroomutils.generic_cache:" + Name, this);
+ }
+ });
+
public Dictionary<string, GenericResult<T>> Cache { get; set; } = new();
public string Name { get; set; } = null!;
- public GenericResult<T> this[string key]
- {
- get
- {
- if (Cache.ContainsKey(key))
- {
+ public GenericResult<T> this[string key] {
+ get {
+ if (Cache.ContainsKey(key)) {
// Console.WriteLine($"cache.get({key}): hit");
// Console.WriteLine($"Found item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
if (Cache[key].ExpiryTime < DateTime.Now)
- Console.WriteLine($"WARNING: item {key} in cache {Name} expired at {Cache[key].ExpiryTime}:\n{Cache[key].Result.ToJson(indent: false)}");
+ Console.WriteLine($"WARNING: item {key} in cache {Name} expired at {Cache[key].ExpiryTime}:\n{Cache[key].Result.ToJson(false)}");
return Cache[key];
}
Console.WriteLine($"cache.get({key}): miss");
return null;
}
- set
- {
- Cache[key] = value;
- // Console.WriteLine($"set({key}) = {Cache[key].Result.ToJson(indent:false)}");
- // Console.WriteLine($"new_state: {this.ToJson(indent:false)}");
- // Console.WriteLine($"New item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
- // Console.Error.WriteLine("Full cache: " + Cache.ToJson());
- }
- }
-
- public ObjectCache()
- {
- //expiry timer
- Task.Run(async () =>
- {
- while (Cache.Any())
- {
- await Task.Delay(1000);
- foreach (var x in Cache.Where(x => x.Value.ExpiryTime < DateTime.Now).OrderBy(x => x.Value.ExpiryTime).Take(15).ToList())
- {
- // Console.WriteLine($"Removing {x.Key} from cache");
- Cache.Remove(x.Key);
- }
- //RuntimeCache.SaveObject("rory.matrixroomutils.generic_cache:" + Name, this);
- }
- });
+ set => Cache[key] = value;
+ // Console.WriteLine($"set({key}) = {Cache[key].Result.ToJson(indent:false)}");
+ // Console.WriteLine($"new_state: {this.ToJson(indent:false)}");
+ // Console.WriteLine($"New item in cache: {key} - {Cache[key].Result.ToJson(indent: false)}");
+ // Console.Error.WriteLine("Full cache: " + Cache.ToJson());
}
public bool ContainsKey(string key) => Cache.ContainsKey(key);
}
-public class GenericResult<T>
-{
- public T? Result { get; set; }
- public DateTime? ExpiryTime { get; set; } = DateTime.Now;
-
- public GenericResult()
- {
+public class GenericResult<T> {
+ public GenericResult() {
//expiry timer
}
- public GenericResult(T? result, DateTime? expiryTime = null) : this()
- {
+ public GenericResult(T? result, DateTime? expiryTime = null) : this() {
Result = result;
ExpiryTime = expiryTime;
}
+
+ public T? Result { get; set; }
+ public DateTime? ExpiryTime { get; set; } = DateTime.Now;
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs
index 6321fb6..a8c1fac 100644
--- a/MatrixRoomUtils.Core/StateEvent.cs
+++ b/MatrixRoomUtils.Core/StateEvent.cs
@@ -4,52 +4,48 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core;
-public class StateEvent
-{
- [JsonPropertyName("content")] public dynamic Content { get; set; } = new { };
+public class StateEvent {
+ [JsonPropertyName("content")]
+ public dynamic Content { get; set; } = new { };
+
+ [JsonPropertyName("state_key")]
+ public string StateKey { get; set; } = "";
- [JsonPropertyName("state_key")] public string StateKey { get; set; } = "";
- [JsonPropertyName("type")] public string Type { get; set; }
- [JsonPropertyName("replaces_state")] public string? ReplacesState { get; set; }
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("replaces_state")]
+ public string? ReplacesState { get; set; }
//extra properties
[JsonIgnore]
- public JsonNode ContentAsJsonNode
- {
+ public JsonNode ContentAsJsonNode {
get => JsonSerializer.SerializeToNode(Content);
set => Content = value;
}
- public StateEvent<T> As<T>() where T : class
- {
- return (StateEvent<T>)this;
- }
-
- public string dtype
- {
- get
- {
- string res = GetType().Name switch
- {
+ public string dtype {
+ get {
+ var res = GetType().Name switch {
"StateEvent`1" => $"StateEvent<{Content.GetType().Name}>",
_ => GetType().Name
};
return res;
}
}
+
+ public StateEvent<T> As<T>() where T : class => (StateEvent<T>)this;
}
-public class StateEvent<T> : StateEvent where T : class
-{
- public StateEvent()
- {
+public class StateEvent<T> : StateEvent where T : class {
+ public StateEvent() {
//import base content if not an empty object
- if (base.Content.GetType() == typeof(T))
- {
+ if (base.Content.GetType() == typeof(T)) {
Console.WriteLine($"StateEvent<{typeof(T)}> created with base content of type {base.Content.GetType()}. Importing base content.");
Content = base.Content;
}
}
+
[JsonPropertyName("content")]
public new T Content { get; set; }
}
\ No newline at end of file
diff --git a/MatrixRoomUtils.Core/StateEventStruct.cs b/MatrixRoomUtils.Core/StateEventStruct.cs
index bfda594..cd301ac 100644
--- a/MatrixRoomUtils.Core/StateEventStruct.cs
+++ b/MatrixRoomUtils.Core/StateEventStruct.cs
@@ -1,7 +1,6 @@
namespace MatrixRoomUtils.Core;
-public struct StateEventStruct
-{
+public struct StateEventStruct {
public object content { get; set; }
public long origin_server_ts { get; set; }
public string sender { get; set; }
diff --git a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
index a927ace..6f6d082 100644
--- a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
+++ b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs
@@ -2,51 +2,50 @@ using System.Text.Json.Serialization;
namespace MatrixRoomUtils.Core.StateEventTypes;
-public class PolicyRuleStateEventData
-{
+public class PolicyRuleStateEventData {
/// <summary>
- /// Entity this ban applies to, can use * and ? as globs.
+ /// Entity this ban applies to, can use * and ? as globs.
/// </summary>
[JsonPropertyName("entity")]
public string Entity { get; set; }
+
/// <summary>
- /// Reason this user is banned
+ /// Reason this user is banned
/// </summary>
[JsonPropertyName("reason")]
public string? Reason { get; set; }
+
/// <summary>
- /// Suggested action to take
+ /// Suggested action to take
/// </summary>
[JsonPropertyName("recommendation")]
public string? Recommendation { get; set; }
/// <summary>
- /// Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry.
+ /// Expiry time in milliseconds since the unix epoch, or null if the ban has no expiry.
/// </summary>
[JsonPropertyName("support.feline.policy.expiry.rev.2")] //stable prefix: expiry, msc pending
public long? Expiry { get; set; }
-
-
+
//utils
/// <summary>
- /// Readable expiry time, provided for easy interaction
+ /// Readable expiry time, provided for easy interaction
/// </summary>
[JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")]
- public DateTime? ExpiryDateTime
- {
+ public DateTime? ExpiryDateTime {
get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime;
- set => Expiry = ((DateTimeOffset) value).ToUnixTimeMilliseconds();
+ set => Expiry = ((DateTimeOffset)value).ToUnixTimeMilliseconds();
}
}
-public static class PolicyRecommendationTypes
-{
+public static class PolicyRecommendationTypes {
/// <summary>
- /// Ban this user
+ /// Ban this user
/// </summary>
public static string Ban = "m.ban";
+
/// <summary>
- /// Mute this user
+ /// Mute this user
/// </summary>
public static string Mute = "support.feline.policy.recommendation_mute"; //stable prefix: m.mute, msc pending
}
\ No newline at end of file
|