From 1cbcf84174f8fdbd021f8e16466d2784e8fdf38c Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 4 Oct 2024 19:46:45 +0200 Subject: Minor cleanups, support for loading access tokens from disk or appservice --- ArcaneLibs | 2 +- LibMatrix.EventTypes/EventContent.cs | 4 + .../Spec/RoomMessageEventContent.cs | 1 + LibMatrix/Extensions/MatrixHttpClient.Single.cs | 16 ++- LibMatrix/Helpers/MessageBuilder.cs | 12 +++ LibMatrix/Helpers/SyncHelper.cs | 8 +- LibMatrix/LibMatrix.csproj | 2 + LibMatrix/Responses/SyncResponse.cs | 2 +- LibMatrix/RoomTypes/GenericRoom.cs | 23 ++++- LibMatrix/Services/ServiceInstaller.cs | 10 -- LibMatrix/StateEvent.cs | 2 +- Utilities/LibMatrix.TestDataGenerator/Program.cs | 1 + .../AppServiceConfiguration.cs | 87 ---------------- .../AppServices/AppServiceConfiguration.cs | 111 +++++++++++++++++++++ .../LibMatrix.Utilities.Bot/BotCommandInstaller.cs | 17 +++- .../LibMatrixBotConfiguration.cs | 3 +- 16 files changed, 189 insertions(+), 112 deletions(-) delete mode 100644 Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs create mode 100644 Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs diff --git a/ArcaneLibs b/ArcaneLibs index b7685c7..952f5ef 160000 --- a/ArcaneLibs +++ b/ArcaneLibs @@ -1 +1 @@ -Subproject commit b7685c786b29e7f8ae2db6ff0f79a52efc57020c +Subproject commit 952f5ef673862fdb7ff6f51d032eca4577dab9c8 diff --git a/LibMatrix.EventTypes/EventContent.cs b/LibMatrix.EventTypes/EventContent.cs index c582cf2..d65a47f 100644 --- a/LibMatrix.EventTypes/EventContent.cs +++ b/LibMatrix.EventTypes/EventContent.cs @@ -37,6 +37,10 @@ public abstract class TimelineEventContent : EventContent { [JsonPropertyName("rel_type")] public string? RelationType { get; set; } + // used for reactions + [JsonPropertyName("key")] + public string? Key { get; set; } + public class EventInReplyTo { [JsonPropertyName("event_id")] public string? EventId { get; set; } diff --git a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs index ae893f8..9602bf3 100644 --- a/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs +++ b/LibMatrix.EventTypes/Spec/RoomMessageEventContent.cs @@ -29,6 +29,7 @@ public class RoomMessageEventContent : TimelineEventContent { [JsonPropertyName("url")] public string? Url { get; set; } + [JsonPropertyName("filename")] public string? FileName { get; set; } [JsonPropertyName("info")] diff --git a/LibMatrix/Extensions/MatrixHttpClient.Single.cs b/LibMatrix/Extensions/MatrixHttpClient.Single.cs index 39eb7e5..4145a16 100644 --- a/LibMatrix/Extensions/MatrixHttpClient.Single.cs +++ b/LibMatrix/Extensions/MatrixHttpClient.Single.cs @@ -2,6 +2,7 @@ // #define SYNC_HTTPCLIENT // Only allow one request as a time, for debugging using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Net.Http.Headers; using System.Reflection; using System.Security.Cryptography.X509Certificates; @@ -73,12 +74,15 @@ public class MatrixHttpClient { await _rateLimitSemaphore.WaitAsync(cancellationToken); #endif - Console.WriteLine($"Sending {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.Content?.Headers.ContentLength ?? 0)})"); + Console.WriteLine($"Sending {request.Method} {BaseAddress}{request.RequestUri} ({Util.BytesToString(request.GetContentLength())})"); if (request.RequestUri is null) throw new NullReferenceException("RequestUri is null"); if (!request.RequestUri.IsAbsoluteUri) request.RequestUri = new Uri(BaseAddress, request.RequestUri); foreach (var (key, value) in AdditionalQueryParameters) request.RequestUri = request.RequestUri.AddQuery(key, value); - foreach (var (key, value) in DefaultRequestHeaders) request.Headers.Add(key, value); + foreach (var (key, value) in DefaultRequestHeaders) { + if (request.Headers.Contains(key)) continue; + request.Headers.Add(key, value); + } request.Options.Set(new HttpRequestOptionsKey("WebAssemblyEnableStreamingResponse"), true); @@ -106,7 +110,13 @@ public class MatrixHttpClient { public async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) { var responseMessage = await SendUnhandledAsync(request, cancellationToken); if (responseMessage.IsSuccessStatusCode) return responseMessage; - + + //retry on gateway timeout + if (responseMessage.StatusCode == HttpStatusCode.GatewayTimeout) { + request.ResetSendStatus(); + return await SendAsync(request, cancellationToken); + } + //error handling var content = await responseMessage.Content.ReadAsStringAsync(cancellationToken); if (content.Length == 0) diff --git a/LibMatrix/Helpers/MessageBuilder.cs b/LibMatrix/Helpers/MessageBuilder.cs index d897078..b639e1f 100644 --- a/LibMatrix/Helpers/MessageBuilder.cs +++ b/LibMatrix/Helpers/MessageBuilder.cs @@ -91,6 +91,18 @@ public class MessageBuilder(string msgType = "m.text", string format = "org.matr return this; } + public MessageBuilder WithMention(string id, string? displayName = null) { + Content.Body += $"@{displayName ?? id}"; + Content.FormattedBody += $"{displayName ?? id}"; + return this; + } + + public MessageBuilder WithNewline() { + Content.Body += "\n"; + Content.FormattedBody += "
"; + return this; + } + public MessageBuilder WithTable(Action tableBuilder) { var tb = new TableBuilder(this); this.WithHtmlTag("table", msb => tableBuilder(tb)); diff --git a/LibMatrix/Helpers/SyncHelper.cs b/LibMatrix/Helpers/SyncHelper.cs index 1833bd0..c9ca85d 100644 --- a/LibMatrix/Helpers/SyncHelper.cs +++ b/LibMatrix/Helpers/SyncHelper.cs @@ -4,6 +4,7 @@ using ArcaneLibs.Extensions; using LibMatrix.Filters; using LibMatrix.Homeservers; using LibMatrix.Responses; +using LibMatrix.Utilities; using Microsoft.Extensions.Logging; namespace LibMatrix.Helpers; @@ -42,6 +43,7 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg _filter = value; _filterIsDirty = true; _filterId = null; + _namedFilterName = null; } } @@ -81,16 +83,16 @@ public class SyncHelper(AuthenticatedHomeserverGeneric homeserver, ILogger? logg if (!string.IsNullOrWhiteSpace(Since)) url += $"&since={Since}"; if (_filterId is not null) url += $"&filter={_filterId}"; - logger?.LogInformation("SyncHelper: Calling: {}", url); + // logger?.LogInformation("SyncHelper: Calling: {}", url); try { var httpResp = await homeserver.ClientHttpClient.GetAsync(url, cancellationToken ?? CancellationToken.None); if (httpResp is null) throw new NullReferenceException("Failed to send HTTP request"); - logger?.LogInformation("Got sync response: {} bytes, {} elapsed", httpResp.Content.Headers.ContentLength ?? -1, sw.Elapsed); + logger?.LogTrace("Got sync response: {} bytes, {} elapsed", httpResp.GetContentLength(), sw.Elapsed); var deserializeSw = Stopwatch.StartNew(); var resp = await httpResp.Content.ReadFromJsonAsync(cancellationToken: cancellationToken ?? CancellationToken.None, jsonTypeInfo: SyncResponseSerializerContext.Default.SyncResponse); - logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.Content.Headers.ContentLength ?? -1, deserializeSw.Elapsed, sw.Elapsed); + logger?.LogInformation("Deserialized sync response: {} bytes, {} elapsed, {} total", httpResp.GetContentLength(), deserializeSw.Elapsed, sw.Elapsed); var timeToWait = MinimumDelay.Subtract(sw.Elapsed); if (timeToWait.TotalMilliseconds > 0) await Task.Delay(timeToWait); diff --git a/LibMatrix/LibMatrix.csproj b/LibMatrix/LibMatrix.csproj index 1e3a8aa..6158ff8 100644 --- a/LibMatrix/LibMatrix.csproj +++ b/LibMatrix/LibMatrix.csproj @@ -27,8 +27,10 @@ + diff --git a/LibMatrix/Responses/SyncResponse.cs b/LibMatrix/Responses/SyncResponse.cs index e4addb6..b2308c5 100644 --- a/LibMatrix/Responses/SyncResponse.cs +++ b/LibMatrix/Responses/SyncResponse.cs @@ -39,7 +39,7 @@ public class SyncResponse { // supporting classes public class PresenceDataStructure { [JsonPropertyName("events")] - public List Events { get; set; } = new(); + public List? Events { get; set; } } public class RoomsDataStructure { diff --git a/LibMatrix/RoomTypes/GenericRoom.cs b/LibMatrix/RoomTypes/GenericRoom.cs index 2ec8571..8398ab9 100644 --- a/LibMatrix/RoomTypes/GenericRoom.cs +++ b/LibMatrix/RoomTypes/GenericRoom.cs @@ -431,6 +431,16 @@ public class GenericRoom { return await res.Content.ReadFromJsonAsync(); } + + public async Task GetRoomAccountDataOrNullAsync(string key) { + try { + return await GetRoomAccountDataAsync(key); + } + catch (MatrixException e) { + if (e.ErrorCode == "M_NOT_FOUND") return default; + throw; + } + } public async Task SetRoomAccountDataAsync(string key, object data) { var res = await Homeserver.ClientHttpClient.PutAsJsonAsync($"/_matrix/client/v3/user/{Homeserver.UserId}/rooms/{RoomId}/account_data/{key}", data); @@ -443,10 +453,17 @@ public class GenericRoom { public Task GetEventAsync(string eventId) => Homeserver.ClientHttpClient.GetFromJsonAsync($"/_matrix/client/v3/rooms/{RoomId}/event/{eventId}"); - public async Task RedactEventAsync(string eventToRedact, string reason) { + public async Task RedactEventAsync(string eventToRedact, string? reason = null) { var data = new { reason }; - return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync( - $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid()}", data)).Content.ReadFromJsonAsync())!; + var url = $"/_matrix/client/v3/rooms/{RoomId}/redact/{eventToRedact}/{Guid.NewGuid().ToString()}"; + while (true) { + try { + return (await (await Homeserver.ClientHttpClient.PutAsJsonAsync(url, data)).Content.ReadFromJsonAsync())!; + } catch (MatrixException e) { + if (e is { ErrorCode: MatrixException.ErrorCodes.M_FORBIDDEN }) throw; + throw; + } + } } #endregion diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs index 06ea9de..8b7e54b 100644 --- a/LibMatrix/Services/ServiceInstaller.cs +++ b/LibMatrix/Services/ServiceInstaller.cs @@ -5,23 +5,13 @@ namespace LibMatrix.Services; public static class ServiceInstaller { public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services, RoryLibMatrixConfiguration? config = null) { - //Check required services - // if (!services.Any(x => x.ServiceType == typeof(TieredStorageService))) - // throw new Exception("[RMUCore/DI] No TieredStorageService has been registered!"); //Add config services.AddSingleton(config ?? new RoryLibMatrixConfiguration()); //Add services services.AddSingleton(sp => new HomeserverResolverService(sp.GetRequiredService>())); - - // if (services.First(x => x.ServiceType == typeof(TieredStorageService)).Lifetime == ServiceLifetime.Singleton) { services.AddSingleton(); - // } - // else { - // services.AddScoped(); - // } - // services.AddScoped(); return services; } } diff --git a/LibMatrix/StateEvent.cs b/LibMatrix/StateEvent.cs index 8b44d2c..cc870e4 100644 --- a/LibMatrix/StateEvent.cs +++ b/LibMatrix/StateEvent.cs @@ -166,7 +166,7 @@ public class StateEventResponse : StateEvent { public string? Sender { get; set; } [JsonPropertyName("unsigned")] - public UnsignedData? Unsigned { get; set; } + public JsonObject? Unsigned { get; set; } [JsonPropertyName("event_id")] public string? EventId { get; set; } diff --git a/Utilities/LibMatrix.TestDataGenerator/Program.cs b/Utilities/LibMatrix.TestDataGenerator/Program.cs index 2583817..f3750a8 100644 --- a/Utilities/LibMatrix.TestDataGenerator/Program.cs +++ b/Utilities/LibMatrix.TestDataGenerator/Program.cs @@ -2,6 +2,7 @@ using LibMatrix.Services; using LibMatrix.Utilities.Bot; +using LibMatrix.Utilities.Bot.AppServices; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TestDataGenerator.Bot; diff --git a/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs deleted file mode 100644 index afda89e..0000000 --- a/Utilities/LibMatrix.Utilities.Bot/AppServiceConfiguration.cs +++ /dev/null @@ -1,87 +0,0 @@ -namespace LibMatrix.Utilities.Bot; - -public class AppServiceConfiguration { - public string Id { get; set; } = null!; - public string? Url { get; set; } = null!; - public string SenderLocalpart { get; set; } = null!; - public string AppserviceToken { get; set; } = null!; - public string HomeserverToken { get; set; } = null!; - public List? Protocols { get; set; } = null!; - public bool? RateLimited { get; set; } = null!; - - public AppserviceNamespaces Namespaces { get; set; } = null!; - - public class AppserviceNamespaces { - public List? Users { get; set; } = null; - public List? Aliases { get; set; } = null; - public List? Rooms { get; set; } = null; - - public class AppserviceNamespace { - public bool Exclusive { get; set; } - public string Regex { get; set; } = null!; - } - } - - /// - /// Please dont look at code, it's horrifying but works - /// - /// - public string ToYaml() { - var yaml = $""" - id: "{Id ?? throw new NullReferenceException("Id is null")}" - url: {(Url is null ? "null" : $"\"{Url}\"")} - as_token: "{AppserviceToken ?? throw new NullReferenceException("AppserviceToken is null")}" - hs_token: "{HomeserverToken ?? throw new NullReferenceException("HomeserverToken is null")}" - sender_localpart: "{SenderLocalpart ?? throw new NullReferenceException("SenderLocalpart is null")}" - - """; - - if (Protocols is not null && Protocols.Count > 0) - yaml += $""" - protocols: - - "{Protocols[0] ?? throw new NullReferenceException("Protocols[0] is null")}" - """; - else - yaml += "protocols: []"; - yaml += "\n"; - if (RateLimited.HasValue) - yaml += $"rate_limited: {RateLimited.Value.ToString().ToLower()}\n"; - else - yaml += "rate_limited: false\n"; - - yaml += "namespaces: \n"; - - if (Namespaces.Users is null || Namespaces.Users.Count == 0) - yaml += " users: []"; - else - Namespaces.Users.ForEach(x => - yaml += $""" - users: - - exclusive: {x.Exclusive.ToString().ToLower()} - regex: "{x.Regex ?? throw new NullReferenceException("x.Regex is null")}" - """); - yaml += "\n"; - - if (Namespaces.Aliases is null || Namespaces.Aliases.Count == 0) - yaml += " aliases: []"; - else - Namespaces.Aliases.ForEach(x => - yaml += $""" - aliases: - - exclusive: {x.Exclusive.ToString().ToLower()} - regex: "{x.Regex ?? throw new NullReferenceException("x.Regex is null")}" - """); - yaml += "\n"; - if (Namespaces.Rooms is null || Namespaces.Rooms.Count == 0) - yaml += " rooms: []"; - else - Namespaces.Rooms.ForEach(x => - yaml += $""" - rooms: - - exclusive: {x.Exclusive.ToString().ToLower()} - regex: "{x.Regex ?? throw new NullReferenceException("x.Regex is null")}" - """); - - return yaml; - } -} \ No newline at end of file diff --git a/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs new file mode 100644 index 0000000..2cfcf32 --- /dev/null +++ b/Utilities/LibMatrix.Utilities.Bot/AppServices/AppServiceConfiguration.cs @@ -0,0 +1,111 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Utilities.Bot.AppServices; + +public class AppServiceConfiguration { + [JsonPropertyName("id")] + public string Id { get; set; } = null!; + + [JsonPropertyName("url")] + public string? Url { get; set; } = null!; + + [JsonPropertyName("sender_localpart")] + public string SenderLocalpart { get; set; } = null!; + + [JsonPropertyName("as_token")] + public string AppserviceToken { get; set; } = null!; + + [JsonPropertyName("hs_token")] + public string HomeserverToken { get; set; } = null!; + + [JsonPropertyName("protocols")] + public List? Protocols { get; set; } = null!; + + [JsonPropertyName("rate_limited")] + public bool? RateLimited { get; set; } = null!; + + [JsonPropertyName("namespaces")] + public AppserviceNamespaces Namespaces { get; set; } = null!; + + public class AppserviceNamespaces { + [JsonPropertyName("users")] + public List? Users { get; set; } = null; + + [JsonPropertyName("aliases")] + public List? Aliases { get; set; } = null; + + [JsonPropertyName("rooms")] + public List? Rooms { get; set; } = null; + + public class AppserviceNamespace { + [JsonPropertyName("exclusive")] + public bool Exclusive { get; set; } + + [JsonPropertyName("regex")] + public string Regex { get; set; } = null!; + } + } + + /// + /// Please dont look at code, it's horrifying but works + /// + /// + public string ToYaml() { + var yaml = $""" + id: "{Id ?? throw new NullReferenceException("Id is null")}" + url: {(Url is null ? "null" : $"\"{Url}\"")} + as_token: "{AppserviceToken ?? throw new NullReferenceException("AppserviceToken is null")}" + hs_token: "{HomeserverToken ?? throw new NullReferenceException("HomeserverToken is null")}" + sender_localpart: "{SenderLocalpart ?? throw new NullReferenceException("SenderLocalpart is null")}" + + """; + + if (Protocols is not null && Protocols.Count > 0) + yaml += $""" + protocols: + - "{Protocols[0] ?? throw new NullReferenceException("Protocols[0] is null")}" + """; + else + yaml += "protocols: []"; + yaml += "\n"; + if (RateLimited.HasValue) + yaml += $"rate_limited: {RateLimited.Value.ToString().ToLower()}\n"; + else + yaml += "rate_limited: false\n"; + + yaml += "namespaces: \n"; + + if (Namespaces.Users is null || Namespaces.Users.Count == 0) + yaml += " users: []"; + else + Namespaces.Users.ForEach(x => + yaml += $""" + users: + - exclusive: {x.Exclusive.ToString().ToLower()} + regex: "{x.Regex ?? throw new NullReferenceException("x.Regex is null")}" + """); + yaml += "\n"; + + if (Namespaces.Aliases is null || Namespaces.Aliases.Count == 0) + yaml += " aliases: []"; + else + Namespaces.Aliases.ForEach(x => + yaml += $""" + aliases: + - exclusive: {x.Exclusive.ToString().ToLower()} + regex: "{x.Regex ?? throw new NullReferenceException("x.Regex is null")}" + """); + yaml += "\n"; + if (Namespaces.Rooms is null || Namespaces.Rooms.Count == 0) + yaml += " rooms: []"; + else + Namespaces.Rooms.ForEach(x => + yaml += $""" + rooms: + - exclusive: {x.Exclusive.ToString().ToLower()} + regex: "{x.Regex ?? throw new NullReferenceException("x.Regex is null")}" + """); + + return yaml; + } +} \ No newline at end of file diff --git a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs b/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs index 621c1ee..ca6a4d8 100644 --- a/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs +++ b/Utilities/LibMatrix.Utilities.Bot/BotCommandInstaller.cs @@ -1,8 +1,7 @@ using ArcaneLibs; -using LibMatrix.EventTypes.Spec.State; using LibMatrix.Homeservers; -using LibMatrix.Responses; using LibMatrix.Services; +using LibMatrix.Utilities.Bot.AppServices; using LibMatrix.Utilities.Bot.Interfaces; using LibMatrix.Utilities.Bot.Services; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +21,20 @@ public class BotInstaller(IServiceCollection services) { services.AddScoped(x => { var config = x.GetService() ?? throw new Exception("No configuration found!"); var hsProvider = x.GetService() ?? throw new Exception("No homeserver provider found!"); + + if (x.GetService() is AppServiceConfiguration appsvcConfig) + config.AccessToken = appsvcConfig.AppserviceToken; + else if (Environment.GetEnvironmentVariable("LIBMATRIX_ACCESS_TOKEN_PATH") is string path) + config.AccessTokenPath = path; + + if(string.IsNullOrWhiteSpace(config.AccessToken) && string.IsNullOrWhiteSpace(config.AccessTokenPath)) + throw new Exception("Unable to add bot service without an access token or access token path!"); + + if(!string.IsNullOrWhiteSpace(config.AccessTokenPath)) { + var token = File.ReadAllText(config.AccessTokenPath); + config.AccessToken = token; + } + var hs = hsProvider.GetAuthenticatedWithToken(config.Homeserver, config.AccessToken).Result; return hs; diff --git a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs b/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs index 245442f..728b169 100644 --- a/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs +++ b/Utilities/LibMatrix.Utilities.Bot/LibMatrixBotConfiguration.cs @@ -6,7 +6,8 @@ namespace LibMatrix.Utilities.Bot; public class LibMatrixBotConfiguration { public LibMatrixBotConfiguration(IConfiguration config) => config.GetRequiredSection("LibMatrixBot").Bind(this); public string Homeserver { get; set; } - public string AccessToken { get; set; } + public string? AccessToken { get; set; } + public string? AccessTokenPath { get; set; } public List Prefixes { get; set; } public bool MentionPrefix { get; set; } public string? LogRoom { get; set; } -- cgit 1.5.1