diff options
Diffstat (limited to 'MatrixRoomUtils.Core')
-rw-r--r-- | MatrixRoomUtils.Core/Authentication/MatrixAccount.cs | 93 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs | 14 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/Extensions/StringExtensions.cs | 17 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj | 9 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/Responses/LoginResponse.cs | 31 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/Responses/ProfileResponse.cs | 11 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/StateEvent.cs | 53 | ||||
-rw-r--r-- | MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs | 52 |
8 files changed, 280 insertions, 0 deletions
diff --git a/MatrixRoomUtils.Core/Authentication/MatrixAccount.cs b/MatrixRoomUtils.Core/Authentication/MatrixAccount.cs new file mode 100644 index 0000000..4180df5 --- /dev/null +++ b/MatrixRoomUtils.Core/Authentication/MatrixAccount.cs @@ -0,0 +1,93 @@ +using System.Net.Http.Json; +using System.Text.Json; +using MatrixRoomUtils.Responses; + +namespace MatrixRoomUtils.Authentication; + +public class MatrixAccount +{ + public static async Task<LoginResponse> Login(string homeserver, string username, string password) + { + Console.WriteLine($"Logging in to {homeserver} as {username}..."); + homeserver = await ResolveHomeserverFromWellKnown(homeserver); + var hc = new HttpClient(); + var payload = new + { + type = "m.login.password", + identifier = new + { + type = "m.id.user", + user = username + }, + 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)) + { + Console.WriteLine($"Login: Waiting {retryAfter.GetInt32()}ms before retrying"); + await Task.Delay(retryAfter.GetInt32()); + return await Login(homeserver, username, password); + } + + return data.Deserialize<LoginResponse>(); + //var token = data.GetProperty("access_token").GetString(); + //return token; + } + + public static async Task<ProfileResponse> GetProfile(string homeserver, string mxid) + { + Console.WriteLine($"Fetching profile for {mxid} on {homeserver}..."); + homeserver = await ResolveHomeserverFromWellKnown(homeserver); + var hc = new HttpClient(); + var resp = await hc.GetAsync($"{homeserver}/_matrix/client/r0/profile/{mxid}"); + var data = await resp.Content.ReadFromJsonAsync<JsonElement>(); + if (!resp.IsSuccessStatusCode) Console.WriteLine("Profile: " + data.ToString()); + return data.Deserialize<ProfileResponse>(); + } + + public static async Task<string> ResolveHomeserverFromWellKnown(string homeserver) + { + using var hc = new HttpClient(); + Console.WriteLine($"Resolving homeserver: {homeserver}"); + if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver; + + if (await CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) + { + var resp = await hc.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/client"); + var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString(); + return hs; + } + Console.WriteLine($"No client well-known..."); + if (await CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) + { + var resp = await hc.GetFromJsonAsync<JsonElement>($"{homeserver}/.well-known/matrix/server"); + var hs = resp.GetProperty("m.server").GetString(); + return hs; + } + Console.WriteLine($"No server well-known..."); + if (await CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) return homeserver; + Console.WriteLine($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!"); + throw new InvalidDataException($"Failed to resolve homeserver, not on {homeserver}, nor do client or server well-knowns exist!"); + } + + private static async Task<bool> CheckSuccessStatus(string url) + { + //cors causes failure, try to catch + try + { + using var hc = new HttpClient(); + var resp = await hc.GetAsync(url); + return resp.IsSuccessStatusCode; + } + catch (Exception e) + { + Console.WriteLine($"Failed to check success status: {e.Message}"); + return false; + } + } +} \ No newline at end of file diff --git a/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..cf798ce --- /dev/null +++ b/MatrixRoomUtils.Core/Extensions/ObjectExtensions.cs @@ -0,0 +1,14 @@ +using System.Text.Json; + +namespace MatrixRoomUtils.Extensions; + +public static class ObjectExtensions +{ + public static string ToJson(this object obj, bool indent = true, bool ignoreNull = false) + { + var jso = new JsonSerializerOptions(); + if(indent) jso.WriteIndented = true; + if(ignoreNull) jso.IgnoreNullValues = true; + 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 new file mode 100644 index 0000000..e02f0b9 --- /dev/null +++ b/MatrixRoomUtils.Core/Extensions/StringExtensions.cs @@ -0,0 +1,17 @@ +using MatrixRoomUtils.Authentication; + +namespace MatrixRoomUtils.Extensions; + +public static class StringExtensions +{ + public static async Task<string> GetMediaUrl(this string MxcUrl) + { + //MxcUrl: mxc://rory.gay/ocRVanZoUTCcifcVNwXgbtTg + //target: https://matrix.rory.gay/_matrix/media/v3/download/rory.gay/ocRVanZoUTCcifcVNwXgbtTg + + var server = MxcUrl.Split('/')[2]; + var mediaId = MxcUrl.Split('/')[3]; + return $"{await MatrixAccount.ResolveHomeserverFromWellKnown(server)}/_matrix/media/v3/download/{server}/{mediaId}"; + } + +} \ No newline at end of file diff --git a/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj b/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj new file mode 100644 index 0000000..6836c68 --- /dev/null +++ b/MatrixRoomUtils.Core/MatrixRoomUtils.Core.csproj @@ -0,0 +1,9 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net7.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + +</Project> diff --git a/MatrixRoomUtils.Core/Responses/LoginResponse.cs b/MatrixRoomUtils.Core/Responses/LoginResponse.cs new file mode 100644 index 0000000..eedc970 --- /dev/null +++ b/MatrixRoomUtils.Core/Responses/LoginResponse.cs @@ -0,0 +1,31 @@ +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using MatrixRoomUtils.Authentication; + +namespace MatrixRoomUtils.Responses; + +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() + { + 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()); + return data.Deserialize<ProfileResponse>(); + } + public async Task<string> GetCanonicalHomeserverUrl() + { + return await MatrixAccount.ResolveHomeserverFromWellKnown(HomeServer); + } +} \ No newline at end of file diff --git a/MatrixRoomUtils.Core/Responses/ProfileResponse.cs b/MatrixRoomUtils.Core/Responses/ProfileResponse.cs new file mode 100644 index 0000000..ab6cc92 --- /dev/null +++ b/MatrixRoomUtils.Core/Responses/ProfileResponse.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace MatrixRoomUtils.Authentication; + +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/StateEvent.cs b/MatrixRoomUtils.Core/StateEvent.cs new file mode 100644 index 0000000..34cefe4 --- /dev/null +++ b/MatrixRoomUtils.Core/StateEvent.cs @@ -0,0 +1,53 @@ +namespace MatrixRoomUtils; + +public class StateEvent +{ + //example: + /* + { + "content": { + "avatar_url": "mxc://matrix.org/BnmEjNvGAkStmAoUiJtEbycT", + "displayname": "X ⊂ Shekhinah | she/her | you", + "membership": "join" + }, + "origin_server_ts": 1682668449785, + "room_id": "!wDPwzxYCNPTkHGHCFT:the-apothecary.club", + "sender": "@kokern:matrix.org", + "state_key": "@kokern:matrix.org", + "type": "m.room.member", + "unsigned": { + "replaces_state": "$7BWfzN15LN8FFUing1hiUQWFfxnOusrEHYFNiOnNrlM", + "prev_content": { + "avatar_url": "mxc://matrix.org/hEQbGywixsjpxDrWvUYEFNur", + "displayname": "X ⊂ Shekhinah | she/her | you", + "membership": "join" + }, + "prev_sender": "@kokern:matrix.org" + }, + "event_id": "$6AGoMCaxqcOeIIDbez1f0VKwLkOEq3EiVLdlsoxDpNg", + "user_id": "@kokern:matrix.org", + "replaces_state": "$7BWfzN15LN8FFUing1hiUQWFfxnOusrEHYFNiOnNrlM", + "prev_content": { + "avatar_url": "mxc://matrix.org/hEQbGywixsjpxDrWvUYEFNur", + "displayname": "X ⊂ Shekhinah | she/her | you", + "membership": "join" + } + } + */ + public dynamic content { get; set; } + public long origin_server_ts { get; set; } + public string room_id { get; set; } + public string sender { get; set; } + public string state_key { get; set; } + public string type { get; set; } + public dynamic unsigned { get; set; } + public string event_id { get; set; } + public string user_id { get; set; } + public string replaces_state { get; set; } + public dynamic prev_content { get; set; } +} + +public class StateEvent<T> : StateEvent where T : class +{ + public T content { get; set; } +} \ No newline at end of file diff --git a/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs new file mode 100644 index 0000000..45063cc --- /dev/null +++ b/MatrixRoomUtils.Core/StateEventTypes/PolicyRuleStateEventData.cs @@ -0,0 +1,52 @@ +using System.Text.Json.Serialization; + +namespace MatrixRoomUtils.StateEventTypes; + +public class PolicyRuleStateEventData +{ + /// <summary> + /// Entity this ban applies to, can use * and ? as globs. + /// </summary> + [JsonPropertyName("entity")] + public string? Entity { get; set; } + /// <summary> + /// Reason this user is banned + /// </summary> + [JsonPropertyName("reason")] + public string? Reason { get; set; } + /// <summary> + /// 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. + /// </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 + /// </summary> + [JsonPropertyName("gay.rory.matrix_room_utils.readable_expiry_time_utc")] + public DateTime? ExpiryDateTime + { + get => Expiry == null ? null : DateTimeOffset.FromUnixTimeMilliseconds(Expiry.Value).DateTime; + set => Expiry = ((DateTimeOffset) value).ToUnixTimeMilliseconds(); + } +} + +public static class PolicyRecommendationTypes +{ + /// <summary> + /// Ban this user + /// </summary> + public static string Ban = "m.ban"; + /// <summary> + /// 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 |