From 48a11be7b7ce140db4264dd4d8a1d6adfad37df3 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 19 Nov 2024 00:10:12 +0100 Subject: [PATCH 01/18] update to .net 9 --- .config/dotnet-tools.json | 2 +- .../Api/DiscordRequestService.cs | 11 +- .../Responders/CustomInteractionResponder.cs | 23 +- Catalogger.Backend/Catalogger.Backend.csproj | 21 +- .../Database/Redis/RedisService.cs | 6 +- .../Extensions/StartupExtensions.cs | 2 - Catalogger.Backend/Program.cs | 7 +- Catalogger.Backend/packages.lock.json | 198 ++++++------------ .../Catalogger.GoImporter.csproj | 2 +- 9 files changed, 99 insertions(+), 173 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index b315638..085fcbc 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "csharpier": { - "version": "0.29.2", + "version": "0.30.1", "commands": [ "dotnet-csharpier" ], diff --git a/Catalogger.Backend/Api/DiscordRequestService.cs b/Catalogger.Backend/Api/DiscordRequestService.cs index abde930..c63e04e 100644 --- a/Catalogger.Backend/Api/DiscordRequestService.cs +++ b/Catalogger.Backend/Api/DiscordRequestService.cs @@ -30,8 +30,10 @@ public class DiscordRequestService private readonly IClock _clock; private readonly ApiTokenRepository _tokenRepository; - private static readonly JsonSerializerOptions JsonOptions = - new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + }; public DiscordRequestService( ILogger logger, @@ -82,8 +84,9 @@ public class DiscordRequestService } private static readonly Uri DiscordUserUri = new("https://discord.com/api/v10/users/@me"); - private static readonly Uri DiscordGuildsUri = - new("https://discord.com/api/v10/users/@me/guilds"); + private static readonly Uri DiscordGuildsUri = new( + "https://discord.com/api/v10/users/@me/guilds" + ); private static readonly Uri DiscordTokenUri = new("https://discord.com/api/oauth2/token"); public async Task GetMeAsync(string token) => await GetAsync(DiscordUserUri, token); diff --git a/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs b/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs index b038cfe..0281302 100644 --- a/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs +++ b/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs @@ -45,18 +45,17 @@ public class CustomInteractionResponder( { private readonly ILogger _logger = logger.ForContext(); - private readonly InteractionResponder _inner = - new( - commandService, - options, - interactionAPI, - eventCollector, - services, - contextInjection, - tokenizerOptions, - treeSearchOptions, - treeNameResolver - ); + private readonly InteractionResponder _inner = new( + commandService, + options, + interactionAPI, + eventCollector, + services, + contextInjection, + tokenizerOptions, + treeSearchOptions, + treeNameResolver + ); public async Task RespondAsync( IInteractionCreate gatewayEvent, diff --git a/Catalogger.Backend/Catalogger.Backend.csproj b/Catalogger.Backend/Catalogger.Backend.csproj index 1e06c3d..884845e 100644 --- a/Catalogger.Backend/Catalogger.Backend.csproj +++ b/Catalogger.Backend/Catalogger.Backend.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable true @@ -21,26 +21,25 @@ - - + + - + - - - - + + + + - - + + - diff --git a/Catalogger.Backend/Database/Redis/RedisService.cs b/Catalogger.Backend/Database/Redis/RedisService.cs index f493a4a..e8e474c 100644 --- a/Catalogger.Backend/Database/Redis/RedisService.cs +++ b/Catalogger.Backend/Database/Redis/RedisService.cs @@ -24,8 +24,10 @@ public class RedisService(Config config) config.Database.Redis! ); - private readonly JsonSerializerOptions _options = - new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; + private readonly JsonSerializerOptions _options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + }; public IDatabase GetDatabase(int db = -1) => _multiplexer.GetDatabase(db); diff --git a/Catalogger.Backend/Extensions/StartupExtensions.cs b/Catalogger.Backend/Extensions/StartupExtensions.cs index aef61b0..c1af3ab 100644 --- a/Catalogger.Backend/Extensions/StartupExtensions.cs +++ b/Catalogger.Backend/Extensions/StartupExtensions.cs @@ -274,8 +274,6 @@ public static class StartupExtensions app.UseSerilogRequestLogging(); app.UseRouting(); app.UseHttpMetrics(); - app.UseSwagger(); - app.UseSwaggerUI(); app.UseCors(); app.UseMiddleware(); app.UseMiddleware(); diff --git a/Catalogger.Backend/Program.cs b/Catalogger.Backend/Program.cs index 94e785a..7f948fc 100644 --- a/Catalogger.Backend/Program.cs +++ b/Catalogger.Backend/Program.cs @@ -120,12 +120,7 @@ builder.Services.AddMetricServer(o => o.Port = (ushort)config.Logging.MetricsPor if (!config.Logging.EnableMetrics) builder.Services.AddHostedService(); -builder - .Services.MaybeAddDashboardServices(config) - .MaybeAddRedisCaches(config) - .AddCustomServices() - .AddEndpointsApiExplorer() - .AddSwaggerGen(); +builder.Services.MaybeAddDashboardServices(config).MaybeAddRedisCaches(config).AddCustomServices(); var app = builder.Build(); diff --git a/Catalogger.Backend/packages.lock.json b/Catalogger.Backend/packages.lock.json index 2af0ac1..f0dbb85 100644 --- a/Catalogger.Backend/packages.lock.json +++ b/Catalogger.Backend/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net8.0": { + "net9.0": { "Dapper": { "type": "Direct", "requested": "[2.1.35, )", @@ -26,22 +26,26 @@ }, "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { "type": "Direct", - "requested": "[8.0.8, )", - "resolved": "8.0.8", - "contentHash": "KL3lI8GmCnnROwDrbWbboVpHiXSNTyoLgYPdHus3hEjAwhSAm1JU5S+rmZk7w3Qt0rQfHVIFxKwCf6yapeZy+w==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "pTFDEmZi3GheCSPrBxzyE63+d5unln2vYldo/nOm1xet/4rpEk2oJYcwpclPQ13E+LZBF9XixkgwYTUwqznlWg==", "dependencies": { - "Microsoft.AspNetCore.JsonPatch": "8.0.8", + "Microsoft.AspNetCore.JsonPatch": "9.0.0", "Newtonsoft.Json": "13.0.3", "Newtonsoft.Json.Bson": "1.0.2" } }, - "Microsoft.AspNetCore.OpenApi": { + "Microsoft.Extensions.Caching.Memory": { "type": "Direct", - "requested": "[8.0.8, )", - "resolved": "8.0.8", - "contentHash": "wNHhohqP8rmsQ4UhKbd6jZMD6l+2Q/+DvRBT0Cgqeuglr13aF6sSJWicZKCIhZAUXzuhkdwtHVc95MlPlFk0dA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "zbnPX/JQ0pETRSUG9fNPBvpIq42Aufvs15gGYyNIMhCun9yhmWihz0WgsI7bSDPjxWTKBf8oX/zv6v2uZ3W9OQ==", "dependencies": { - "Microsoft.OpenApi": "1.4.3" + "Microsoft.Extensions.Caching.Abstractions": "9.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Logging.Abstractions": "9.0.0", + "Microsoft.Extensions.Options": "9.0.0", + "Microsoft.Extensions.Primitives": "9.0.0" } }, "Newtonsoft.Json": { @@ -52,12 +56,9 @@ }, "NodaTime": { "type": "Direct", - "requested": "[3.1.12, )", - "resolved": "3.1.12", - "contentHash": "nDcUbG0jiEXmV8cOz7V8GnUKlmPJjqZm/R+E2JNnUSdlMoaQ19xSU8GXFLReGs/Nt8xdBfA8XfO77xVboWO1Vg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.7.1" - } + "requested": "[3.2.0, )", + "resolved": "3.2.0", + "contentHash": "yoRA3jEJn8NM0/rQm78zuDNPA3DonNSZdsorMUj+dltc1D+/Lc5h9YXGqbEEZozMGr37lAoYkcSM/KjTVqD0ow==" }, "NodaTime.Serialization.SystemTextJson": { "type": "Direct", @@ -70,36 +71,37 @@ }, "Npgsql": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "zRG5V8cyeZLpzJlKzFKjEwkRMYIYnHWJvEor2lWXeccS2E1G2nIWYYhnukB51iz5XsWSVEtqg3AxTWM0QJ6vfg==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "zu1nCRt0gWP/GR0reYgg0Bl5o8qyNV7mVAgzAbVLRiAd1CYXcf/9nrubPH0mt93u8iGTKmYqWaLVECEAcE6IfQ==", "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" + "Microsoft.Extensions.Logging.Abstractions": "8.0.2", + "System.Text.Json": "9.0.0" } }, "Npgsql.NodaTime": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "oC7Ml5TDuQlcGECB5ML0XsPxFrYu3OdpG7c9cuqhB+xunLvqbZ0zXQoPJjvXK9KDNPDB/II61HNdsNas9f2J3A==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "kHVcgTeJ68MUufMs3HAboklhM5v+qHwVNqh4Tkko/BPP3wkijGXaUaHYffgaga9n+bIIHq1f1VdTl99Rz7XxFA==", "dependencies": { - "NodaTime": "3.1.9", - "Npgsql": "8.0.5" + "NodaTime": "3.2.0", + "Npgsql": "9.0.0" } }, "Polly.Core": { "type": "Direct", - "requested": "[8.4.2, )", - "resolved": "8.4.2", - "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g==" + "requested": "[8.5.0, )", + "resolved": "8.5.0", + "contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A==" }, "Polly.RateLimiting": { "type": "Direct", - "requested": "[8.4.2, )", - "resolved": "8.4.2", - "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==", + "requested": "[8.5.0, )", + "resolved": "8.5.0", + "contentHash": "ChVprxvWs0QU90Aiu9LwcEJCoDwYhx46zDFQkA5ODhCjDPTyLKKU/ODjHFlqkrSogZ0sZIIAS6FuM93yEKSAZQ==", "dependencies": { - "Polly.Core": "8.4.2", + "Polly.Core": "8.5.0", "System.Threading.RateLimiting": "8.0.0" } }, @@ -144,21 +146,21 @@ }, "Serilog": { "type": "Direct", - "requested": "[4.0.2, )", - "resolved": "4.0.2", - "contentHash": "Vehq4uNYtURe/OnHEpWGvMgrvr5Vou7oZLdn3BuEH5FSCeHXDpNJtpzWoqywXsSvCTuiv0I65mZDRnJSeUvisA==" + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" }, "Serilog.AspNetCore": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "LNUd1bHsik2E7jSoCQFdeMGAWXjH7eUQ6c2pqm5vl+jGqvxdabYXxlrfaqApjtX5+BfAjW9jTA2EKmPwxknpIA==", + "requested": "[8.0.3, )", + "resolved": "8.0.3", + "contentHash": "Y5at41mc0OV982DEJslBKHd6uzcWO6POwR3QceJ6gtpMPxCzm4+FElGPF0RdaTD7MGsP6XXE05LMbSi0NO+sXg==", "dependencies": { "Microsoft.Extensions.Logging": "8.0.0", "Serilog": "3.1.1", "Serilog.Extensions.Hosting": "8.0.0", "Serilog.Formatting.Compact": "2.0.0", - "Serilog.Settings.Configuration": "8.0.2", + "Serilog.Settings.Configuration": "8.0.4", "Serilog.Sinks.Console": "5.0.0", "Serilog.Sinks.Debug": "2.0.0", "Serilog.Sinks.File": "5.0.0" @@ -206,18 +208,6 @@ "Pipelines.Sockets.Unofficial": "2.2.8" } }, - "Swashbuckle.AspNetCore": { - "type": "Direct", - "requested": "[6.8.1, )", - "resolved": "6.8.1", - "contentHash": "JN6ccH37QKtNOwBrvSxc+jBYIB+cw6RlZie2IKoJhjjf6HzBH+2kPJCpxmJ5EHIqmxvq6aQG+0A8XklGx9rAxA==", - "dependencies": { - "Microsoft.Extensions.ApiDescription.Server": "6.0.5", - "Swashbuckle.AspNetCore.Swagger": "6.8.1", - "Swashbuckle.AspNetCore.SwaggerGen": "6.8.1", - "Swashbuckle.AspNetCore.SwaggerUI": "6.8.1" - } - }, "CommunityToolkit.HighPerformance": { "type": "Transitive", "resolved": "8.2.2", @@ -235,8 +225,8 @@ }, "Microsoft.AspNetCore.JsonPatch": { "type": "Transitive", - "resolved": "8.0.8", - "contentHash": "IGhuO/SsjHIIvFP4O/5pn/WcPJor+A+BERBhIkMYrlYcRXnZmbBBNSyqoNI9wFq0oxtsrnYMnzXAIi+0MKVdSA==", + "resolved": "9.0.0", + "contentHash": "/4UONYoAIeexPoAmbzBPkVGA6KAY7t0BM+1sr0fKss2V1ERCdcM+Llub4X5Ma+LJ60oPp6KzM0e3j+Pp/JHCNw==", "dependencies": { "Microsoft.CSharp": "4.7.0", "Newtonsoft.Json": "13.0.3" @@ -247,29 +237,12 @@ "resolved": "4.7.0", "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" }, - "Microsoft.Extensions.ApiDescription.Server": { - "type": "Transitive", - "resolved": "6.0.5", - "contentHash": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==" - }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==", + "resolved": "9.0.0", + "contentHash": "FPWZAa9c0H4dvOj351iR1jkUIs4u9ykL4Bm592yhjDyO5lCoWd+TMAHx2EMbarzUvCvgjWjJIoC6//Q9kH6YhA==", "dependencies": { - "Microsoft.Extensions.Primitives": "8.0.0" - } - }, - "Microsoft.Extensions.Caching.Memory": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "7pqivmrZDzo1ADPkRwjy+8jtRKWRCPag9qPI+p7sgu7Q4QreWhcvbiWXsbhP+yY8XSiDvZpu2/LWdBv7PnmOpQ==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Configuration": { @@ -307,17 +280,13 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + "resolved": "9.0.0", + "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "5Ou6varcxLBzQ+Agfm0k0pnH7vrEITYlXMDuE6s7ZHlZHz6/G8XJ3iISZDr5rfwfge6RnXJ1+Wc479mMn52vjA==", - "dependencies": { - "System.Text.Encodings.Web": "8.0.0", - "System.Text.Json": "8.0.4" - } + "resolved": "8.0.2", + "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", @@ -394,10 +363,10 @@ }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "resolved": "9.0.0", + "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" } }, "Microsoft.Extensions.ObjectPool": { @@ -407,11 +376,11 @@ }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "dWGKvhFybsaZpGmzkGCbNNwBD1rVlWzrZKANLW/CcbFJpCEceMCGzT7zZwHOGBCbwM0SzBuceMj5HN1LKV1QqA==", + "resolved": "9.0.0", + "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", + "Microsoft.Extensions.Primitives": "9.0.0" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { @@ -428,13 +397,8 @@ }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" - }, - "Microsoft.OpenApi": { - "type": "Transitive", - "resolved": "1.6.14", - "contentHash": "tTaBT8qjk3xINfESyOPE2rIellPvB7qpVqiWiyA/lACVvz+xOGiXhFUfohcx82NLbi5avzLW0lx+s6oAqQijfw==" + "resolved": "9.0.0", + "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" }, "Newtonsoft.Json.Bson": { "type": "Transitive", @@ -658,11 +622,11 @@ }, "Serilog.Settings.Configuration": { "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "hn8HCAmupon7N0to20EwGeNJ+L3iRzjGzAHIl8+8CCFlEkVedHvS6NMYMb0VPNMsDgDwOj4cPBPV6Fc2hb0/7w==", + "resolved": "8.0.4", + "contentHash": "pkxvq0umBKK8IKFJc1aV5S/HGRG/NIxJ6FV42KaTPLfDmBOAbBUB1m5gqqlGxzEa1MgDDWtQlWJdHTSxVWNx+Q==", "dependencies": { "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Microsoft.Extensions.DependencyModel": "8.0.1", + "Microsoft.Extensions.DependencyModel": "8.0.2", "Serilog": "3.1.1" } }, @@ -682,27 +646,6 @@ "Serilog": "2.10.0" } }, - "Swashbuckle.AspNetCore.Swagger": { - "type": "Transitive", - "resolved": "6.8.1", - "contentHash": "eOkdM4bsWBU5Ty3kWbyq5O9L+05kZT0vOdGh4a92vIb/LLQGQTPLRHXuJdnUBNIPNC8XfKWfSbtRfqzI6nnbqw==", - "dependencies": { - "Microsoft.OpenApi": "1.6.14" - } - }, - "Swashbuckle.AspNetCore.SwaggerGen": { - "type": "Transitive", - "resolved": "6.8.1", - "contentHash": "TjBPxsN0HeJzxEXZYeDXBNNMSyhg+TYXtkbwX+Cn8GH/y5ZeoB/chw0p71kRo5tR2sNshbKwL24T6f9pTF9PHg==", - "dependencies": { - "Swashbuckle.AspNetCore.Swagger": "6.8.1" - } - }, - "Swashbuckle.AspNetCore.SwaggerUI": { - "type": "Transitive", - "resolved": "6.8.1", - "contentHash": "lpEszYJ7vZaTTE5Dp8MrsbSHrgDfjhDMjzW1qOA1Xs1Dnj3ZRBJAcPZUTsa5Bva+nLaw91JJ8OI8FkSg8hhIyA==" - }, "System.ComponentModel.Annotations": { "type": "Transitive", "resolved": "5.0.0", @@ -718,23 +661,10 @@ "resolved": "5.0.1", "contentHash": "qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==" }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "zOHkQmzPCn5zm/BH+cxC1XbUS3P4Yoi3xzW7eRgVpDR2tPGSzyMZ17Ig1iRkfJuY0nhxkQQde8pgePNiA7z7TQ==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" - }, "System.Text.Json": { "type": "Transitive", - "resolved": "8.0.4", - "contentHash": "bAkhgDJ88XTsqczoxEMliSrpijKZHhbJQldhAmObj/RbrN3sU5dcokuXmWJWsdQAhiMJ9bTayWsL1C9fbbCRhw==", - "dependencies": { - "System.Text.Encodings.Web": "8.0.0" - } + "resolved": "9.0.0", + "contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A==" }, "System.Threading.Channels": { "type": "Transitive", diff --git a/Catalogger.GoImporter/Catalogger.GoImporter.csproj b/Catalogger.GoImporter/Catalogger.GoImporter.csproj index f948b03..4464d0c 100644 --- a/Catalogger.GoImporter/Catalogger.GoImporter.csproj +++ b/Catalogger.GoImporter/Catalogger.GoImporter.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable From b3c541f743747742144199ca044f4dadb8cebc12 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 19 Nov 2024 15:32:09 +0100 Subject: [PATCH 02/18] fix: don't mark cross compiled builds as dirty --- Catalogger.Backend/BuildInfo.cs | 2 ++ build_info.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Catalogger.Backend/BuildInfo.cs b/Catalogger.Backend/BuildInfo.cs index 2c55730..95ca05f 100644 --- a/Catalogger.Backend/BuildInfo.cs +++ b/Catalogger.Backend/BuildInfo.cs @@ -40,5 +40,7 @@ public static class BuildInfo Version = versionData[0]; if (versionData[1] != "0" || dirty) Version += $"+{versionData[2]}"; + if (dirty) + Version += ".dirty"; } } diff --git a/build_info.sh b/build_info.sh index c644a3c..8dbbed8 100755 --- a/build_info.sh +++ b/build_info.sh @@ -1,4 +1,4 @@ #!/bin/sh (git rev-parse HEAD && git describe --tags --always --long && - if test -z "$(git ls-files --exclude-standard --modified --deleted --others)"; then echo clean; else echo dirty; fi) > ../.version + if test -z "$(git ls-files --exclude-standard --modified --deleted --others -- '*' '!packages.lock.json')"; then echo clean; else echo dirty; fi) > ../.version From 04d6bc958e8b9d2590ecffbf84995f8c8fa255a3 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 19 Nov 2024 15:46:06 +0100 Subject: [PATCH 03/18] just remove the package lock file --- Catalogger.Backend/Catalogger.Backend.csproj | 1 - Catalogger.Backend/packages.lock.json | 681 ------------------- build_info.sh | 2 +- 3 files changed, 1 insertion(+), 683 deletions(-) delete mode 100644 Catalogger.Backend/packages.lock.json diff --git a/Catalogger.Backend/Catalogger.Backend.csproj b/Catalogger.Backend/Catalogger.Backend.csproj index 884845e..6d048ab 100644 --- a/Catalogger.Backend/Catalogger.Backend.csproj +++ b/Catalogger.Backend/Catalogger.Backend.csproj @@ -4,7 +4,6 @@ net9.0 enable enable - true diff --git a/Catalogger.Backend/packages.lock.json b/Catalogger.Backend/packages.lock.json deleted file mode 100644 index f0dbb85..0000000 --- a/Catalogger.Backend/packages.lock.json +++ /dev/null @@ -1,681 +0,0 @@ -{ - "version": 1, - "dependencies": { - "net9.0": { - "Dapper": { - "type": "Direct", - "requested": "[2.1.35, )", - "resolved": "2.1.35", - "contentHash": "YKRwjVfrG7GYOovlGyQoMvr1/IJdn+7QzNXJxyMh0YfFF5yvDmTYaJOVYWsckreNjGsGSEtrMTpnzxTUq/tZQw==" - }, - "Humanizer.Core": { - "type": "Direct", - "requested": "[2.14.1, )", - "resolved": "2.14.1", - "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" - }, - "LazyCache": { - "type": "Direct", - "requested": "[2.4.0, )", - "resolved": "2.4.0", - "contentHash": "THig17vqe5PEs3wvTqFrNzorz2nD4Qz9F9C3YlAydU673CogAO8z1u8NNJD6x52I7oDCQ/N/HwJIZMBH8Y/Qiw==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "2.1.0", - "Microsoft.Extensions.Caching.Memory": "2.1.0" - } - }, - "Microsoft.AspNetCore.Mvc.NewtonsoftJson": { - "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "pTFDEmZi3GheCSPrBxzyE63+d5unln2vYldo/nOm1xet/4rpEk2oJYcwpclPQ13E+LZBF9XixkgwYTUwqznlWg==", - "dependencies": { - "Microsoft.AspNetCore.JsonPatch": "9.0.0", - "Newtonsoft.Json": "13.0.3", - "Newtonsoft.Json.Bson": "1.0.2" - } - }, - "Microsoft.Extensions.Caching.Memory": { - "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "zbnPX/JQ0pETRSUG9fNPBvpIq42Aufvs15gGYyNIMhCun9yhmWihz0WgsI7bSDPjxWTKBf8oX/zv6v2uZ3W9OQ==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "9.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0", - "Microsoft.Extensions.Primitives": "9.0.0" - } - }, - "Newtonsoft.Json": { - "type": "Direct", - "requested": "[13.0.3, )", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, - "NodaTime": { - "type": "Direct", - "requested": "[3.2.0, )", - "resolved": "3.2.0", - "contentHash": "yoRA3jEJn8NM0/rQm78zuDNPA3DonNSZdsorMUj+dltc1D+/Lc5h9YXGqbEEZozMGr37lAoYkcSM/KjTVqD0ow==" - }, - "NodaTime.Serialization.SystemTextJson": { - "type": "Direct", - "requested": "[1.2.0, )", - "resolved": "1.2.0", - "contentHash": "HNMQdHw6xCrNaHEEvJlBek+uUNI4uySEQhU3t8FibZT9ASMz40y5qkLIwhrHsnXhxUzOPP4tmAGy8PfBwc3zMg==", - "dependencies": { - "NodaTime": "[3.0.0, 4.0.0)" - } - }, - "Npgsql": { - "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "zu1nCRt0gWP/GR0reYgg0Bl5o8qyNV7mVAgzAbVLRiAd1CYXcf/9nrubPH0mt93u8iGTKmYqWaLVECEAcE6IfQ==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "8.0.2", - "System.Text.Json": "9.0.0" - } - }, - "Npgsql.NodaTime": { - "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "kHVcgTeJ68MUufMs3HAboklhM5v+qHwVNqh4Tkko/BPP3wkijGXaUaHYffgaga9n+bIIHq1f1VdTl99Rz7XxFA==", - "dependencies": { - "NodaTime": "3.2.0", - "Npgsql": "9.0.0" - } - }, - "Polly.Core": { - "type": "Direct", - "requested": "[8.5.0, )", - "resolved": "8.5.0", - "contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A==" - }, - "Polly.RateLimiting": { - "type": "Direct", - "requested": "[8.5.0, )", - "resolved": "8.5.0", - "contentHash": "ChVprxvWs0QU90Aiu9LwcEJCoDwYhx46zDFQkA5ODhCjDPTyLKKU/ODjHFlqkrSogZ0sZIIAS6FuM93yEKSAZQ==", - "dependencies": { - "Polly.Core": "8.5.0", - "System.Threading.RateLimiting": "8.0.0" - } - }, - "prometheus-net": { - "type": "Direct", - "requested": "[8.2.1, )", - "resolved": "8.2.1", - "contentHash": "3wVgdEPOCBF752s2xps5T+VH+c9mJK8S8GKEDg49084P6JZMumTZI5Te6aJ9MQpX0sx7om6JOnBpIi7ZBmmiDQ==", - "dependencies": { - "Microsoft.Extensions.Http": "3.1.0", - "Microsoft.Extensions.ObjectPool": "7.0.0" - } - }, - "prometheus-net.AspNetCore": { - "type": "Direct", - "requested": "[8.2.1, )", - "resolved": "8.2.1", - "contentHash": "/4TfTvbwIDqpaKTiWvEsjUywiHYF9zZvGZF5sK15avoDsUO/WPQbKsF8TiMaesuphdFQPK2z52P0zk6j26V0rQ==", - "dependencies": { - "prometheus-net": "8.2.1" - } - }, - "Remora.Discord": { - "type": "Direct", - "requested": "[2024.3.0-github11168366508, )", - "resolved": "2024.3.0-github11168366508", - "contentHash": "tlqwVPeILmUmjEIsDgRQQChwCPnwAvpJTXSiYMruPDO+XVomfMjMUfS7EVIMUosHEC4bs4PS8m60lbTO2Lducw==", - "dependencies": { - "Remora.Discord.Caching": "39.0.0-github11168366508", - "Remora.Discord.Commands": "28.1.0-github11168366508", - "Remora.Discord.Extensions": "5.3.6-github11168366508", - "Remora.Discord.Hosting": "6.0.10-github11168366508", - "Remora.Discord.Interactivity": "5.0.0-github11168366508", - "Remora.Discord.Pagination": "4.0.1-github11168366508" - } - }, - "Remora.Sdk": { - "type": "Direct", - "requested": "[3.1.2, )", - "resolved": "3.1.2", - "contentHash": "IjHGwOH9XZJu4sMPA25M/gMLJktq4CdtSvekn8sAF85bE/3uhxU9pqmuzc4N39ktY7aTkLBRDa6/oQJnmiI6CQ==" - }, - "Serilog": { - "type": "Direct", - "requested": "[4.1.0, )", - "resolved": "4.1.0", - "contentHash": "u1aZI8HZ62LWlq5dZLFwm6jMax/sUwnWZSw5lkPsCt518cJBxFKoNmc7oSxe5aA5BgSkzy9rzwFGR/i/acnSPw==" - }, - "Serilog.AspNetCore": { - "type": "Direct", - "requested": "[8.0.3, )", - "resolved": "8.0.3", - "contentHash": "Y5at41mc0OV982DEJslBKHd6uzcWO6POwR3QceJ6gtpMPxCzm4+FElGPF0RdaTD7MGsP6XXE05LMbSi0NO+sXg==", - "dependencies": { - "Microsoft.Extensions.Logging": "8.0.0", - "Serilog": "3.1.1", - "Serilog.Extensions.Hosting": "8.0.0", - "Serilog.Formatting.Compact": "2.0.0", - "Serilog.Settings.Configuration": "8.0.4", - "Serilog.Sinks.Console": "5.0.0", - "Serilog.Sinks.Debug": "2.0.0", - "Serilog.Sinks.File": "5.0.0" - } - }, - "Serilog.Extensions.Hosting": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "db0OcbWeSCvYQkHWu6n0v40N4kKaTAXNjlM3BKvcbwvNzYphQFcBR+36eQ/7hMMwOkJvAyLC2a9/jNdUL5NjtQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Serilog": "3.1.1", - "Serilog.Extensions.Logging": "8.0.0" - } - }, - "Serilog.Sinks.Console": { - "type": "Direct", - "requested": "[6.0.0, )", - "resolved": "6.0.0", - "contentHash": "fQGWqVMClCP2yEyTXPIinSr5c+CBGUvBybPxjAGcf7ctDhadFhrQw03Mv8rJ07/wR5PDfFjewf2LimvXCDzpbA==", - "dependencies": { - "Serilog": "4.0.0" - } - }, - "Serilog.Sinks.Seq": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "z5ig56/qzjkX6Fj4U/9m1g8HQaQiYPMZS4Uevtjg1I+WWzoGSf5t/E+6JbMP/jbZYhU63bA5NJN5y0x+qqx2Bw==", - "dependencies": { - "Serilog": "4.0.0", - "Serilog.Sinks.File": "5.0.0" - } - }, - "StackExchange.Redis": { - "type": "Direct", - "requested": "[2.8.16, )", - "resolved": "2.8.16", - "contentHash": "WaoulkOqOC9jHepca3JZKFTqndCWab5uYS7qCzmiQDlrTkFaDN7eLSlEfHycBxipRnQY9ppZM7QSsWAwUEGblw==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Pipelines.Sockets.Unofficial": "2.2.8" - } - }, - "CommunityToolkit.HighPerformance": { - "type": "Transitive", - "resolved": "8.2.2", - "contentHash": "+zIp8d3sbtYaRbM6hqDs4Ui/z34j7DcUmleruZlYLE4CVxXq+MO8XJyIs42vzeTYFX+k0Iq1dEbBUnQ4z/Gnrw==" - }, - "FuzzySharp": { - "type": "Transitive", - "resolved": "2.0.2", - "contentHash": "sBKqWxw3g//peYxDZ8JipRlyPbIyBtgzqBVA5GqwHVeqtIrw75maGXAllztf+1aJhchD+drcQIgf2mFho8ZV8A==" - }, - "JsonDocumentPath": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "4mgdlioVvfq6ZjftvsoKANWgpr/AU+UySiW68EjcbPbTfvcrZOlgS+6JkouRAN4TwI8dN2DUAVME7bklThk3KQ==" - }, - "Microsoft.AspNetCore.JsonPatch": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "/4UONYoAIeexPoAmbzBPkVGA6KAY7t0BM+1sr0fKss2V1ERCdcM+Llub4X5Ma+LJ60oPp6KzM0e3j+Pp/JHCNw==", - "dependencies": { - "Microsoft.CSharp": "4.7.0", - "Newtonsoft.Json": "13.0.3" - } - }, - "Microsoft.CSharp": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" - }, - "Microsoft.Extensions.Caching.Abstractions": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "FPWZAa9c0H4dvOj351iR1jkUIs4u9ykL4Bm592yhjDyO5lCoWd+TMAHx2EMbarzUvCvgjWjJIoC6//Q9kH6YhA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.0" - } - }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "0J/9YNXTMWSZP2p2+nvl8p71zpSwokZXZuJW+VjdErkegAnFdO1XlqtA62SJtgVYHdKu3uPxJHcMR/r35HwFBA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "8.0.0" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "mBMoXLsr5s1y2zOHWmKsE9veDcx8h1x/c3rz4baEdQKTeDcmQAPNbB54Pi/lhFO3K431eEq6PFbMgLaa6PHFfA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "+6f2qv2a3dLwd5w6JanPIPs47CxRbnk+ZocMJUhv9NxP88VlOcJYZs9jY+MYSjxvady08bUZn6qgiNh7DadGgg==" - }, - "Microsoft.Extensions.DependencyModel": { - "type": "Transitive", - "resolved": "8.0.2", - "contentHash": "mUBDZZRgZrSyFOsJ2qJJ9fXfqd/kXJwf3AiDoqLD9m6TjY5OO/vLNOb9fb4juC0487eq4hcGN/M2Rh/CKS7QYw==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3PZp/YSkIXrF7QK7PfC1bkyRYwqOHpWFad8Qx+4wkuumAeXo1NHaxpS9LboNA9OvNSAu+QOVlXbMyoY+pHSqcw==", - "dependencies": { - "Microsoft.Extensions.Configuration": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", - "Microsoft.Extensions.Options.ConfigurationExtensions": "8.0.0" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0" - } - }, - "Microsoft.Extensions.FileProviders.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "ZbaMlhJlpisjuWbvXr4LdAst/1XxH3vZ6A0BsgTphZ2L4PGuxRLz7Jr/S7mkAAnOn78Vu0fKhEgNF5JO3zfjqQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "8.0.0" - } - }, - "Microsoft.Extensions.Hosting.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0" - } - }, - "Microsoft.Extensions.Http": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "cWz4caHwvx0emoYe7NkHPxII/KkTI8R/LC9qdqJqnKv2poTJ4e2qqPGQqvRoQ5kaSA4FU5IV3qFAuLuOhoqULQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Diagnostics": "8.0.0", - "Microsoft.Extensions.Logging": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" - } - }, - "Microsoft.Extensions.Http.Polly": { - "type": "Transitive", - "resolved": "8.0.6", - "contentHash": "vehhL2uDlr2ovIFMuYcQwXgOCu7QECXnjcRD37luN40Fjqm0C4PDiN0t0dHoyfJp6OgJ+sOYDev5jVMGz4lJnQ==", - "dependencies": { - "Microsoft.Extensions.Http": "8.0.0", - "Polly": "7.2.4", - "Polly.Extensions.Http": "3.0.0" - } - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "8.0.0", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "g0UfujELzlLbHoVG8kPKVBaW470Ewi+jnptGS9KUi6jcb+k2StujtK3m26DFSGGwQ/+bVgZfsWqNzlP6YOejvw==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0" - } - }, - "Microsoft.Extensions.ObjectPool": { - "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "udvKco0sAVgYGTBnHUb0tY9JQzJ/nPDiv/8PIyz69wl1AibeCDZOLVVI+6156dPfHmJH7ws5oUJRiW4ZmAvuuA==" - }, - "Microsoft.Extensions.Options": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "y2146b3jrPI3Q0lokKXdKLpmXqakYbDIPDV6r3M8SqvSf45WwOTzkyfDpxnZXJsJQEpAsAqjUq5Pu8RCJMjubg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Primitives": "9.0.0" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "0f4DMRqEd50zQh+UyJc+/HiBsZ3vhAQALgdkcQEalSH1L2isdC7Yj54M3cyo5e+BeO5fcBQ7Dxly8XiBBcvRgw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", - "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "Microsoft.Extensions.Options": "8.0.0", - "Microsoft.Extensions.Primitives": "8.0.0" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "N3qEBzmLMYiASUlKxxFIISP4AiwuPTHF5uCh+2CWSwwzAJiIYx0kBJsS30cp1nvhSySFAVi30jecD307jV+8Kg==" - }, - "Newtonsoft.Json.Bson": { - "type": "Transitive", - "resolved": "1.0.2", - "contentHash": "QYFyxhaABwmq3p/21VrZNYvCg3DaEoN/wUuw5nmfAf0X3HLjgupwhkEWdgfb9nvGAUIv3osmZoD3kKl4jxEmYQ==", - "dependencies": { - "Newtonsoft.Json": "12.0.1" - } - }, - "NGettext": { - "type": "Transitive", - "resolved": "0.6.7", - "contentHash": "gT6bf5PVayvTuEIuM2XSNqthrtn9W+LlCX4RD//Nb4hrT3agohHvPdjpROgNGgyXDkjwE74F+EwDwqUgJCJG8A==" - }, - "OneOf": { - "type": "Transitive", - "resolved": "3.0.271", - "contentHash": "pqpqeK8xQGggExhr4tesVgJkjdn+9HQAO0QgrYV2hFjE3y90okzk1kQMntMiUOGfV7FrCUfKPaVvPBD4IANqKg==" - }, - "Pipelines.Sockets.Unofficial": { - "type": "Transitive", - "resolved": "2.2.8", - "contentHash": "zG2FApP5zxSx6OcdJQLbZDk2AVlN2BNQD6MorwIfV6gVj0RRxWPEp2LXAxqDGZqeNV1Zp0BNPcNaey/GXmTdvQ==", - "dependencies": { - "System.IO.Pipelines": "5.0.1" - } - }, - "Polly": { - "type": "Transitive", - "resolved": "8.4.0", - "contentHash": "z2EeUutuy49jBQyZ5s2FUuTCGx3GCzJ0cJ2HbjWwks94TsC6bKTtAHKBkMZOa/DyYRl5yIX7MshvMTWl1J6RNg==", - "dependencies": { - "Polly.Core": "8.4.0" - } - }, - "Polly.Contrib.WaitAndRetry": { - "type": "Transitive", - "resolved": "1.1.1", - "contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA==" - }, - "Polly.Extensions.Http": { - "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==", - "dependencies": { - "Polly": "7.1.0" - } - }, - "Remora.Commands": { - "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "uvZ34ywhK9WxBBqHZiLz7GXJDPZrt0N+IhRs5+V53TTCvLlgA0S8zBCPCANnVpcbVJ8Vl9l3EkcL+PY0VT0TYw==", - "dependencies": { - "Microsoft.Extensions.Options": "8.0.0", - "Remora.Results": "7.4.1" - } - }, - "Remora.Discord.API": { - "type": "Transitive", - "resolved": "78.0.0-github11168366508", - "contentHash": "yDH7x0XLbe4GPhHeK5Ju4tGXCPpSAo0Jd20jikVZOlFHLJkynt0NVWYTT69ZJyniibopwpeANPyAnX8KhZmBbA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "8.0.0", - "Microsoft.Extensions.Options": "8.0.2", - "Remora.Discord.API.Abstractions": "82.0.0-github11168366508", - "Remora.Rest": "3.4.0", - "System.Text.Json": "8.0.3" - } - }, - "Remora.Discord.API.Abstractions": { - "type": "Transitive", - "resolved": "82.0.0-github11168366508", - "contentHash": "vUsvcaM8bSqha9uBhye0mRvARaRHYQgQcIre+CcEloGO4n2JzalLdCFlYIUF3yzcBMGWQnnXymMSzvxjipPglw==", - "dependencies": { - "OneOf": "3.0.271", - "Remora.Rest.Core": "2.2.1", - "Remora.Results": "7.4.1" - } - }, - "Remora.Discord.Caching": { - "type": "Transitive", - "resolved": "39.0.0-github11168366508", - "contentHash": "LY6fROu/g+lcfV60OAM+7KC29nsKtJNUuhiGPI1Mb1w6uR5LoTWGaM29/nQeY8DzixD60np7lF5ZwZUlgoTp0g==", - "dependencies": { - "Remora.Discord.Caching.Abstractions": "1.1.4-github11168366508", - "Remora.Discord.Gateway": "12.0.2-github11168366508" - } - }, - "Remora.Discord.Caching.Abstractions": { - "type": "Transitive", - "resolved": "1.1.4-github11168366508", - "contentHash": "ZDh/C/d0lJ2rYY/8UyRDf57XYg2ZVnTjwuqVXNYrGI/kkQCMI3R4WCbPOppBrycji6iX5pp+fx1j1pSdZsc3eA==", - "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "8.0.0", - "Remora.Results": "7.4.1" - } - }, - "Remora.Discord.Commands": { - "type": "Transitive", - "resolved": "28.1.0-github11168366508", - "contentHash": "SzYCnL4KEsnqvBaDLrXeAfkr45A3cHygJSO/VUSfQpTC6XoHDSMY181H7M2czgY+GiwSzrxYkeu/p89MFkzvxw==", - "dependencies": { - "FuzzySharp": "2.0.2", - "Humanizer.Core": "2.14.1", - "NGettext": "0.6.7", - "Remora.Commands": "10.0.5", - "Remora.Discord.Gateway": "12.0.2-github11168366508", - "Remora.Extensions.Options.Immutable": "1.0.8", - "System.ComponentModel.Annotations": "5.0.0" - } - }, - "Remora.Discord.Extensions": { - "type": "Transitive", - "resolved": "5.3.6-github11168366508", - "contentHash": "xidy4VW5xS8m+crKKjZeN2p6H+TQOgl9Je79ykX1vckMrUOMGtSreKoCEzpVRMPyXotNr9K2xbj1dqNtr4afXw==", - "dependencies": { - "Remora.Discord.API": "78.0.0-github11168366508", - "Remora.Discord.Commands": "28.1.0-github11168366508", - "Remora.Discord.Gateway": "12.0.2-github11168366508", - "Remora.Discord.Interactivity": "5.0.0-github11168366508" - } - }, - "Remora.Discord.Gateway": { - "type": "Transitive", - "resolved": "12.0.2-github11168366508", - "contentHash": "yleE7MHFc8JC6QDhCf6O9Xn2mQA06mmZtwph4tiBnehBTf6GY0ST6op7szEHEE4BI6LuvSo7TuKaHqFzAbxLHQ==", - "dependencies": { - "CommunityToolkit.HighPerformance": "8.2.2", - "Remora.Discord.Rest": "51.0.0-github11168366508", - "Remora.Extensions.Options.Immutable": "1.0.8", - "System.Threading.Channels": "8.0.0" - } - }, - "Remora.Discord.Hosting": { - "type": "Transitive", - "resolved": "6.0.10-github11168366508", - "contentHash": "BCTbNq/sYvUeiuFSNt8Y0aFi0+g4Fnz1vcHEwzFPxczGsW1QaHNOJst8GDpV9fEfcBrs5EHgE+Y4vo0ed8B9zQ==", - "dependencies": { - "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", - "Remora.Discord.Gateway": "12.0.2-github11168366508", - "Remora.Extensions.Options.Immutable": "1.0.8" - } - }, - "Remora.Discord.Interactivity": { - "type": "Transitive", - "resolved": "5.0.0-github11168366508", - "contentHash": "vJOy/8//5+UcTHx8TV4iilQrYJEVfqfmuPNISIShLlgbEzbp/UjmN7QBiOJtpgUAPifeaQbmBXLPlYR0nKEDxg==", - "dependencies": { - "Remora.Discord.Commands": "28.1.0-github11168366508", - "Remora.Discord.Gateway": "12.0.2-github11168366508" - } - }, - "Remora.Discord.Pagination": { - "type": "Transitive", - "resolved": "4.0.1-github11168366508", - "contentHash": "+JKA+GYTlAkX1MxElI+ICGGmZnteiODiVHN09+QeHsjHaWxSBkb7g3pk8OqWrLhyQlyGvI/37kHV+UjRT6Ua5A==", - "dependencies": { - "Remora.Discord.Interactivity": "5.0.0-github11168366508" - } - }, - "Remora.Discord.Rest": { - "type": "Transitive", - "resolved": "51.0.0-github11168366508", - "contentHash": "4NImnAdU27K2Wkbjvw1Dyyib+dZwpKvl39vwnYNnpcYRgQ9mSiKWXq6y2rw/bXXn/l7V/EO6qZsgN1+Q5Yo65A==", - "dependencies": { - "Microsoft.Extensions.Caching.Memory": "8.0.0", - "Microsoft.Extensions.Http.Polly": "8.0.6", - "Polly": "8.4.0", - "Polly.Contrib.WaitAndRetry": "1.1.1", - "Remora.Discord.API": "78.0.0-github11168366508", - "Remora.Discord.Caching.Abstractions": "1.1.4-github11168366508" - } - }, - "Remora.Extensions.Options.Immutable": { - "type": "Transitive", - "resolved": "1.0.8", - "contentHash": "CCw7IlZnE7hCGsO7sb9w05qdYY7bTufdYe6hiXKTOE3IDwdl2xtV7vitMif1KXVAjSZi9QySk8UPA5OfJTC3bA==", - "dependencies": { - "Microsoft.Extensions.Options": "7.0.1" - } - }, - "Remora.Rest": { - "type": "Transitive", - "resolved": "3.4.0", - "contentHash": "uncX4dsj6sq52ZUAnUrUs/usl3YEO4KZ+939r1K6Ojlq2IAZuuJ/4WocicARAiUZp8xa4xeOk1xbAP0+54D3gg==", - "dependencies": { - "JsonDocumentPath": "1.0.3", - "Microsoft.Extensions.Http": "8.0.0", - "OneOf": "3.0.263", - "Remora.Rest.Core": "2.2.1", - "Remora.Results": "7.4.1", - "System.Text.Json": "8.0.3" - } - }, - "Remora.Rest.Core": { - "type": "Transitive", - "resolved": "2.2.1", - "contentHash": "XWhTyHiClwJHiZf0+Ci0+R8ZdeJOyFWvPYh05JNYwAE9327T57d7VIqInbZ8/NfRdgYZ3TSHEjUwITVhetQZZQ==" - }, - "Remora.Results": { - "type": "Transitive", - "resolved": "7.4.1", - "contentHash": "XDO1jZBNpp3d0gApH0uG8BcOkjL4QxMJAEkmx3SlP202GDHev0BthuC4yOcENT5yApZvVT4IV5pJAwLYtSYIFg==" - }, - "Serilog.Extensions.Logging": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==", - "dependencies": { - "Microsoft.Extensions.Logging": "8.0.0", - "Serilog": "3.1.1" - } - }, - "Serilog.Formatting.Compact": { - "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "ob6z3ikzFM3D1xalhFuBIK1IOWf+XrQq+H4KeH4VqBcPpNcmUgZlRQ2h3Q7wvthpdZBBoY86qZOI2LCXNaLlNA==", - "dependencies": { - "Serilog": "3.1.0" - } - }, - "Serilog.Settings.Configuration": { - "type": "Transitive", - "resolved": "8.0.4", - "contentHash": "pkxvq0umBKK8IKFJc1aV5S/HGRG/NIxJ6FV42KaTPLfDmBOAbBUB1m5gqqlGxzEa1MgDDWtQlWJdHTSxVWNx+Q==", - "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "8.0.0", - "Microsoft.Extensions.DependencyModel": "8.0.2", - "Serilog": "3.1.1" - } - }, - "Serilog.Sinks.Debug": { - "type": "Transitive", - "resolved": "2.0.0", - "contentHash": "Y6g3OBJ4JzTyyw16fDqtFcQ41qQAydnEvEqmXjhwhgjsnG/FaJ8GUqF5ldsC/bVkK8KYmqrPhDO+tm4dF6xx4A==", - "dependencies": { - "Serilog": "2.10.0" - } - }, - "Serilog.Sinks.File": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==", - "dependencies": { - "Serilog": "2.10.0" - } - }, - "System.ComponentModel.Annotations": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg==" - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==" - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==" - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "js7+qAu/9mQvnhA4EfGMZNEzXtJCDxgkgj8ohuxq/Qxv+R56G+ljefhiJHOxTNiw54q8vmABCWUwkMulNdlZ4A==" - }, - "System.Threading.Channels": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==" - }, - "System.Threading.RateLimiting": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q==" - } - } - } -} \ No newline at end of file diff --git a/build_info.sh b/build_info.sh index 8dbbed8..c644a3c 100755 --- a/build_info.sh +++ b/build_info.sh @@ -1,4 +1,4 @@ #!/bin/sh (git rev-parse HEAD && git describe --tags --always --long && - if test -z "$(git ls-files --exclude-standard --modified --deleted --others -- '*' '!packages.lock.json')"; then echo clean; else echo dirty; fi) > ../.version + if test -z "$(git ls-files --exclude-standard --modified --deleted --others)"; then echo clean; else echo dirty; fi) > ../.version From c06376dfda0e4c673c76dba477f99ecae66045cc Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 19 Nov 2024 20:30:05 +0100 Subject: [PATCH 04/18] im stupid and NewsService._isExpired could literally never fire --- Catalogger.Backend/Services/NewsService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Catalogger.Backend/Services/NewsService.cs b/Catalogger.Backend/Services/NewsService.cs index e52c783..3adfebe 100644 --- a/Catalogger.Backend/Services/NewsService.cs +++ b/Catalogger.Backend/Services/NewsService.cs @@ -34,8 +34,9 @@ public class NewsService( private readonly ILogger _logger = logger.ForContext(); private List? _messages; + private Instant _lastUpdated = Instant.MinValue; private readonly SemaphoreSlim _lock = new(1); - private bool _isExpired => clock.GetCurrentInstant() > clock.GetCurrentInstant() + ExpiresAfter; + private bool _isExpired => clock.GetCurrentInstant() > _lastUpdated + ExpiresAfter; public async Task> GetNewsAsync() { @@ -74,6 +75,7 @@ public class NewsService( } finally { + _lastUpdated = clock.GetCurrentInstant(); _lock.Release(); } } From 27e77eeaed0fffecdbef62aad9840276339eec2c Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 26 Nov 2024 21:25:10 +0100 Subject: [PATCH 05/18] feat: slightly more debug logs, enrich message events with extra data --- .../Messages/MessageCreateResponder.cs | 2 + .../Messages/MessageDeleteBulkResponder.cs | 2 + .../Messages/MessageDeleteResponder.cs | 20 +++++-- .../Messages/MessageUpdateResponder.cs | 2 + Catalogger.Backend/Extensions/LogUtils.cs | 58 +++++++++++++++++++ 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 Catalogger.Backend/Extensions/LogUtils.cs diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs index 4f2b3a7..5098cd2 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs @@ -38,6 +38,8 @@ public class MessageCreateResponder( public async Task RespondAsync(IMessageCreate msg, CancellationToken ct = default) { + using var __ = LogUtils.Enrich(msg); + userCache.UpdateUser(msg.Author); CataloggerMetrics.MessagesReceived.Inc(); diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteBulkResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteBulkResponder.cs index 46fdd0b..157d30d 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteBulkResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteBulkResponder.cs @@ -41,6 +41,8 @@ public class MessageDeleteBulkResponder( public async Task RespondAsync(IMessageDeleteBulk evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + var guild = await guildRepository.GetAsync(evt.GuildID); if (guild.IsMessageIgnored(evt.ChannelID, null, null)) return Result.Success; diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs index 9cf621e..315daec 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs @@ -27,6 +27,7 @@ using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Rest.Core; using Remora.Results; +using Serilog.Context; namespace Catalogger.Backend.Bot.Responders.Messages; @@ -48,6 +49,8 @@ public class MessageDeleteResponder( public async Task RespondAsync(IMessageDelete evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + if (!evt.GuildID.IsDefined()) return Result.Success; @@ -64,13 +67,15 @@ public class MessageDeleteResponder( return Result.Success; var guild = await guildRepository.GetAsync(evt.GuildID); - if (guild.IsMessageIgnored(evt.ChannelID, null, null)) - return Result.Success; - var msg = await messageRepository.GetMessageAsync(evt.ID.Value, ct); // Sometimes a message that *should* be logged isn't stored in the database, notify the user of that if (msg == null) { + _logger.Debug( + "Deleted message {MessageId} should be logged but is not in the database", + evt.ID + ); + webhookExecutor.QueueLog( webhookExecutor.GetLogChannel(guild, LogChannelType.MessageDelete, evt.ChannelID), new Embed( @@ -106,8 +111,13 @@ public class MessageDeleteResponder( evt.ChannelID, msg.UserId ); - if (logChannel == null) - return Result.Success; + if (logChannel is null or 0) + { + _logger.Debug( + "Message {MessageId} should not be logged; either ignored or message delete logs are disabled", + evt.ID + ); + } var user = await userCache.GetUserAsync(DiscordSnowflake.New(msg.UserId)); var builder = new EmbedBuilder() diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs index 023cab1..fa1e75d 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs @@ -42,6 +42,8 @@ public class MessageUpdateResponder( public async Task RespondAsync(IMessageUpdate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + // Discord only *very* recently changed message update events to have all fields, // so we convert the event to a MessageCreate to avoid having to unwrap every single field var msg = ConvertToMessageCreate(evt); diff --git a/Catalogger.Backend/Extensions/LogUtils.cs b/Catalogger.Backend/Extensions/LogUtils.cs new file mode 100644 index 0000000..b8499b5 --- /dev/null +++ b/Catalogger.Backend/Extensions/LogUtils.cs @@ -0,0 +1,58 @@ +using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Rest.Core; +using Serilog.Context; + +namespace Catalogger.Backend.Extensions; + +public static class LogUtils +{ + public static IDisposable Enrich(T evt) + where T : IGatewayEvent + { + var type = ("Event", typeof(T).Name); + + return evt switch + { + IMessageDelete md => PushProperties( + type, + ("GuildId", md.GuildID), + ("ChannelId", md.ChannelID), + ("MessageId", md.ID) + ), + IMessageUpdate mc => PushProperties( + type, + ("GuildId", mc.GuildID), + ("ChannelId", mc.ChannelID), + ("MessageId", mc.ID) + ), + IMessageDeleteBulk mdb => PushProperties( + type, + ("GuildId", mdb.GuildID), + ("ChannelId", mdb.ChannelID), + ("MessageIds", mdb.IDs) + ), + _ => PushProperties(type), + }; + } + + public static IDisposable PushProperties(params (string, object?)[] properties) => + new MultiDisposable( + properties + .Select(p => + { + if (p.Item2 is Optional s) + return LogContext.PushProperty(p.Item1, s.IsDefined() ? s.Value : null); + return LogContext.PushProperty(p.Item1, p.Item2); + }) + .ToArray() + ); + + private record MultiDisposable(IDisposable[] Entries) : IDisposable + { + public void Dispose() + { + foreach (var e in Entries) + e.Dispose(); + } + } +} From 4047df8610bd79c8056b9a92717bc2e3f677f155 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 27 Nov 2024 15:22:29 +0100 Subject: [PATCH 06/18] fix: always get the latest migration even when two are applied at the same time without also ordering by migration_name, two migrations being applied at the same time *can* (but won't always) result in the *earlier* migration being returned as the latest one. as our migrations are not idempotent, this causes the bot to crash on startup (and even if they *were* idempotent, the code that inserts the new migration name into the migrations table isn't) --- Catalogger.Backend/Database/DatabaseMigrator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Catalogger.Backend/Database/DatabaseMigrator.cs b/Catalogger.Backend/Database/DatabaseMigrator.cs index 80e68c6..183723d 100644 --- a/Catalogger.Backend/Database/DatabaseMigrator.cs +++ b/Catalogger.Backend/Database/DatabaseMigrator.cs @@ -141,7 +141,7 @@ public class DatabaseMigrator(ILogger logger, IClock clock, DatabaseConnection c if (hasMigrationTable) { return await conn.QuerySingleOrDefaultAsync( - "SELECT * FROM migrations ORDER BY applied_at DESC LIMIT 1" + "SELECT * FROM migrations ORDER BY applied_at DESC, migration_name DESC LIMIT 1" ); } @@ -163,7 +163,7 @@ public class DatabaseMigrator(ILogger logger, IClock clock, DatabaseConnection c return await reader.ReadToEndAsync(); } - public static IEnumerable GetMigrationNames() => + private static IEnumerable GetMigrationNames() => typeof(DatabasePool) .Assembly.GetManifestResourceNames() .Where(s => s.StartsWith($"{RootPath}.Migrations")) From 7749c9d9e269c451ddbf6e2cc1e4f2d31697e0f7 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 27 Nov 2024 15:48:30 +0100 Subject: [PATCH 07/18] feat: more log context --- .../Channels/ChannelCreateResponder.cs | 3 +- .../Channels/ChannelDeleteResponder.cs | 2 + .../Channels/ChannelUpdateResponder.cs | 2 + .../Responders/CustomInteractionResponder.cs | 29 ++++-- .../Responders/Guilds/AuditLogResponder.cs | 3 + .../Responders/Guilds/GuildBanAddResponder.cs | 1 + .../Guilds/GuildBanRemoveResponder.cs | 1 + .../Responders/Guilds/GuildCreateResponder.cs | 4 + .../Guilds/GuildEmojisUpdateResponder.cs | 2 + .../Guilds/GuildMembersChunkResponder.cs | 3 + .../Responders/Guilds/GuildUpdateResponder.cs | 2 + .../Invites/InviteCreateResponder.cs | 1 + .../Invites/InviteDeleteResponder.cs | 1 + .../Members/GuildMemberAddResponder.cs | 2 + .../Members/GuildMemberRemoveResponder.cs | 2 + .../Members/GuildMemberUpdateResponder.cs | 2 + .../Bot/Responders/ReadyResponder.cs | 12 +-- .../Responders/Roles/RoleCreateResponder.cs | 2 + .../Responders/Roles/RoleDeleteResponder.cs | 2 + .../Responders/Roles/RoleUpdateResponder.cs | 2 + Catalogger.Backend/Extensions/LogUtils.cs | 96 ++++++++++++++++++- 21 files changed, 159 insertions(+), 15 deletions(-) diff --git a/Catalogger.Backend/Bot/Responders/Channels/ChannelCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Channels/ChannelCreateResponder.cs index 935734a..0fc9d7f 100644 --- a/Catalogger.Backend/Bot/Responders/Channels/ChannelCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Channels/ChannelCreateResponder.cs @@ -17,7 +17,6 @@ using Catalogger.Backend.Cache.InMemoryCache; using Catalogger.Backend.Database.Repositories; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; -using Microsoft.Extensions.Logging.Configuration; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.Extensions.Embeds; @@ -36,6 +35,8 @@ public class ChannelCreateResponder( { public async Task RespondAsync(IChannelCreate ch, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(ch); + if (!ch.GuildID.IsDefined()) return Result.Success; channelCache.Set(ch); diff --git a/Catalogger.Backend/Bot/Responders/Channels/ChannelDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Channels/ChannelDeleteResponder.cs index 7e13bcb..06b4727 100644 --- a/Catalogger.Backend/Bot/Responders/Channels/ChannelDeleteResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Channels/ChannelDeleteResponder.cs @@ -35,6 +35,8 @@ public class ChannelDeleteResponder( public async Task RespondAsync(IChannelDelete evt, CancellationToken ct = default) { + using var __ = LogUtils.Enrich(evt); + if (!evt.GuildID.IsDefined()) { _logger.Debug("Deleted channel {ChannelId} is not in a guild", evt.ID); diff --git a/Catalogger.Backend/Bot/Responders/Channels/ChannelUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Channels/ChannelUpdateResponder.cs index 7ed3954..5f0ef62 100644 --- a/Catalogger.Backend/Bot/Responders/Channels/ChannelUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Channels/ChannelUpdateResponder.cs @@ -40,6 +40,8 @@ public class ChannelUpdateResponder( public async Task RespondAsync(IChannelUpdate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + try { if (!channelCache.TryGet(evt.ID, out var oldChannel)) diff --git a/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs b/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs index 0281302..c07dc6d 100644 --- a/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs +++ b/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs @@ -13,6 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using Catalogger.Backend.Extensions; using Microsoft.Extensions.Options; using Remora.Commands.Services; using Remora.Commands.Tokenization; @@ -23,6 +24,7 @@ using Remora.Discord.Commands.Responders; using Remora.Discord.Commands.Services; using Remora.Discord.Gateway.Responders; using Remora.Results; +using Serilog.Context; namespace Catalogger.Backend.Bot.Responders; @@ -57,21 +59,34 @@ public class CustomInteractionResponder( treeNameResolver ); - public async Task RespondAsync( - IInteractionCreate gatewayEvent, - CancellationToken ct = default - ) + public async Task RespondAsync(IInteractionCreate evt, CancellationToken ct = default) { if (config.Discord.TestMode) { _logger.Information( "Not responding to interaction create event {InteractionId} in {ChannelId} as test mode is enabled", - gatewayEvent.ID, - gatewayEvent.Channel.Map(c => c.ID).OrDefault() + evt.ID, + evt.Channel.Map(c => c.ID).OrDefault() ); return Result.Success; } - return await _inner.RespondAsync(gatewayEvent, ct); + using var _ = LogUtils.PushProperties( + ("Event", nameof(IInteractionCreate)), + ("InteractionId", evt.ID), + ("GuildId", evt.GuildID), + ("UserId", evt.User.Map(u => u.ID)), + ("MemberId", evt.Member.Map(m => m.User.Map(u => u.ID).OrDefault())), + ("ChannelId", evt.Channel.Map(c => c.ID)), + ("InteractionType", evt.Type) + ); + + using var __ = LogContext.PushProperty( + "InteractionData", + evt.Data.HasValue ? (object?)evt.Data.Value : null, + true + ); + + return await _inner.RespondAsync(evt, ct); } } diff --git a/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs index 1d88db1..fba79eb 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/AuditLogResponder.cs @@ -14,6 +14,7 @@ // along with this program. If not, see . using Catalogger.Backend.Cache.InMemoryCache; +using Catalogger.Backend.Extensions; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.Gateway.Responders; @@ -28,6 +29,8 @@ public class AuditLogResponder(AuditLogCache auditLogCache, ILogger logger) public Task RespondAsync(IGuildAuditLogEntryCreate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + if (evt.TargetID == null || evt.UserID == null) return Task.FromResult(Result.Success); diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs index f20d8c2..2feb745 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanAddResponder.cs @@ -37,6 +37,7 @@ public class GuildBanAddResponder( public async Task RespondAsync(IGuildBanAdd evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); var guildConfig = await guildRepository.GetAsync(evt.GuildID); // Delay 2 seconds for the audit log diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs index 99fcce2..cc50908 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildBanRemoveResponder.cs @@ -37,6 +37,7 @@ public class GuildBanRemoveResponder( public async Task RespondAsync(IGuildBanRemove evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); var guildConfig = await guildRepository.GetAsync(evt.GuildID); // Delay 2 seconds for the audit log diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs index 9d5ac61..55bac7a 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildCreateResponder.cs @@ -44,6 +44,8 @@ public class GuildCreateResponder( public async Task RespondAsync(IGuildCreate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + ulong guildId; string? guildName = null; if (evt.Guild.TryPickT0(out var guild, out var unavailableGuild)) @@ -101,6 +103,8 @@ public class GuildCreateResponder( public async Task RespondAsync(IGuildDelete evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + if (evt.IsUnavailable.OrDefault(false)) { _logger.Debug("Guild {GuildId} became unavailable", evt.ID); diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildEmojisUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildEmojisUpdateResponder.cs index 0ccc859..c70bd41 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildEmojisUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildEmojisUpdateResponder.cs @@ -37,6 +37,8 @@ public class GuildEmojisUpdateResponder( public async Task RespondAsync(IGuildEmojisUpdate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + try { if (!emojiCache.TryGet(evt.GuildID, out var oldEmoji)) diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildMembersChunkResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildMembersChunkResponder.cs index 8a7c786..8a43b46 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildMembersChunkResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildMembersChunkResponder.cs @@ -14,6 +14,7 @@ // along with this program. If not, see . using Catalogger.Backend.Cache; +using Catalogger.Backend.Extensions; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.Gateway.Responders; using Remora.Results; @@ -27,6 +28,8 @@ public class GuildMembersChunkResponder(ILogger logger, IMemberCache memberCache public async Task RespondAsync(IGuildMembersChunk evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + _logger.Debug( "Received chunk {ChunkIndex} / {ChunkCount} for guild {GuildId}", evt.ChunkIndex + 1, diff --git a/Catalogger.Backend/Bot/Responders/Guilds/GuildUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Guilds/GuildUpdateResponder.cs index ee43e98..b2992c4 100644 --- a/Catalogger.Backend/Bot/Responders/Guilds/GuildUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Guilds/GuildUpdateResponder.cs @@ -37,6 +37,8 @@ public class GuildUpdateResponder( public async Task RespondAsync(IGuildUpdate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + try { if (!guildCache.TryGet(evt.ID, out var oldGuild)) diff --git a/Catalogger.Backend/Bot/Responders/Invites/InviteCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Invites/InviteCreateResponder.cs index 329d764..d50f81d 100644 --- a/Catalogger.Backend/Bot/Responders/Invites/InviteCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Invites/InviteCreateResponder.cs @@ -37,6 +37,7 @@ public class InviteCreateResponder( public async Task RespondAsync(IInviteCreate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); var guildId = evt.GuildID.Value; var invitesResult = await guildApi.GetGuildInvitesAsync(guildId, ct); diff --git a/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs index bb9d855..d34b3c8 100644 --- a/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Invites/InviteDeleteResponder.cs @@ -38,6 +38,7 @@ public class InviteDeleteResponder( public async Task RespondAsync(IInviteDelete evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); var guildId = evt.GuildID.Value; var dbDeleteCount = await inviteRepository.DeleteInviteAsync(guildId, evt.Code); diff --git a/Catalogger.Backend/Bot/Responders/Members/GuildMemberAddResponder.cs b/Catalogger.Backend/Bot/Responders/Members/GuildMemberAddResponder.cs index b873efe..6d8901c 100644 --- a/Catalogger.Backend/Bot/Responders/Members/GuildMemberAddResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Members/GuildMemberAddResponder.cs @@ -48,6 +48,8 @@ public class GuildMemberAddResponder( public async Task RespondAsync(IGuildMemberAdd member, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(member); + await memberCache.SetAsync(member.GuildID, member); await memberCache.SetMemberNamesAsync(member.GuildID, [member]); diff --git a/Catalogger.Backend/Bot/Responders/Members/GuildMemberRemoveResponder.cs b/Catalogger.Backend/Bot/Responders/Members/GuildMemberRemoveResponder.cs index 94cf8cc..a2e61d1 100644 --- a/Catalogger.Backend/Bot/Responders/Members/GuildMemberRemoveResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Members/GuildMemberRemoveResponder.cs @@ -39,6 +39,8 @@ public class GuildMemberRemoveResponder( public async Task RespondAsync(IGuildMemberRemove evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + try { var embed = new EmbedBuilder() diff --git a/Catalogger.Backend/Bot/Responders/Members/GuildMemberUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Members/GuildMemberUpdateResponder.cs index e02b790..649c945 100644 --- a/Catalogger.Backend/Bot/Responders/Members/GuildMemberUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Members/GuildMemberUpdateResponder.cs @@ -48,6 +48,8 @@ public class GuildMemberUpdateResponder( CancellationToken ct = default ) { + using var _ = LogUtils.Enrich(newMember); + try { var oldMember = await memberCache.TryGetAsync(newMember.GuildID, newMember.User.ID); diff --git a/Catalogger.Backend/Bot/Responders/ReadyResponder.cs b/Catalogger.Backend/Bot/Responders/ReadyResponder.cs index 192ce72..8c43140 100644 --- a/Catalogger.Backend/Bot/Responders/ReadyResponder.cs +++ b/Catalogger.Backend/Bot/Responders/ReadyResponder.cs @@ -26,19 +26,19 @@ public class ReadyResponder(ILogger logger, WebhookExecutorService webhookExecut { private readonly ILogger _logger = logger.ForContext(); - public Task RespondAsync(IReady gatewayEvent, CancellationToken ct = default) + public Task RespondAsync(IReady evt, CancellationToken ct = default) { - var shardId = gatewayEvent.Shard.TryGet(out var shard) - ? (shard.ShardID, shard.ShardCount) - : (0, 1); + using var _ = LogUtils.Enrich(evt); + + var shardId = evt.Shard.TryGet(out var shard) ? (shard.ShardID, shard.ShardCount) : (0, 1); _logger.Information( "Ready as {User} on shard {ShardId}/{ShardCount}", - gatewayEvent.User.Tag(), + evt.User.Tag(), shardId.Item1, shardId.Item2 ); if (shardId.Item1 == 0) - webhookExecutorService.SetSelfUser(gatewayEvent.User); + webhookExecutorService.SetSelfUser(evt.User); return Task.FromResult(Result.Success); } diff --git a/Catalogger.Backend/Bot/Responders/Roles/RoleCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Roles/RoleCreateResponder.cs index 0d09658..8d079d7 100644 --- a/Catalogger.Backend/Bot/Responders/Roles/RoleCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Roles/RoleCreateResponder.cs @@ -35,6 +35,8 @@ public class RoleCreateResponder( public async Task RespondAsync(IGuildRoleCreate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + _logger.Debug("Received new role {RoleId} in guild {GuildId}", evt.Role.ID, evt.GuildID); roleCache.Set(evt.Role, evt.GuildID); diff --git a/Catalogger.Backend/Bot/Responders/Roles/RoleDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Roles/RoleDeleteResponder.cs index f76b339..5f9b648 100644 --- a/Catalogger.Backend/Bot/Responders/Roles/RoleDeleteResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Roles/RoleDeleteResponder.cs @@ -35,6 +35,8 @@ public class RoleDeleteResponder( public async Task RespondAsync(IGuildRoleDelete evt, CancellationToken ct = default) { + using var __ = LogUtils.Enrich(evt); + try { if (!roleCache.TryGet(evt.RoleID, out var role)) diff --git a/Catalogger.Backend/Bot/Responders/Roles/RoleUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Roles/RoleUpdateResponder.cs index 4ed8c2f..656bb02 100644 --- a/Catalogger.Backend/Bot/Responders/Roles/RoleUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Roles/RoleUpdateResponder.cs @@ -37,6 +37,8 @@ public class RoleUpdateResponder( public async Task RespondAsync(IGuildRoleUpdate evt, CancellationToken ct = default) { + using var _ = LogUtils.Enrich(evt); + try { var newRole = evt.Role; diff --git a/Catalogger.Backend/Extensions/LogUtils.cs b/Catalogger.Backend/Extensions/LogUtils.cs index b8499b5..330e5f7 100644 --- a/Catalogger.Backend/Extensions/LogUtils.cs +++ b/Catalogger.Backend/Extensions/LogUtils.cs @@ -1,4 +1,5 @@ using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; using Serilog.Context; @@ -19,7 +20,13 @@ public static class LogUtils ("ChannelId", md.ChannelID), ("MessageId", md.ID) ), - IMessageUpdate mc => PushProperties( + IMessageUpdate mu => PushProperties( + type, + ("GuildId", mu.GuildID), + ("ChannelId", mu.ChannelID), + ("MessageId", mu.ID) + ), + IMessageCreate mc => PushProperties( type, ("GuildId", mc.GuildID), ("ChannelId", mc.ChannelID), @@ -31,6 +38,93 @@ public static class LogUtils ("ChannelId", mdb.ChannelID), ("MessageIds", mdb.IDs) ), + IGuildRoleCreate grc => PushProperties( + type, + ("GuildId", grc.GuildID), + ("RoleId", grc.Role.ID) + ), + IGuildRoleUpdate gru => PushProperties( + type, + ("GuildId", gru.GuildID), + ("RoleId", gru.Role.ID) + ), + IGuildRoleDelete grd => PushProperties( + type, + ("GuildId", grd.GuildID), + ("RoleId", grd.RoleID) + ), + IGuildMemberAdd gma => PushProperties( + type, + ("GuildId", gma.GuildID), + ("UserId", gma.User.Map(u => u.ID)) + ), + IGuildMemberUpdate gmu => PushProperties( + type, + ("GuildId", gmu.GuildID), + ("UserId", gmu.User.ID) + ), + IGuildMemberRemove gmr => PushProperties( + type, + ("GuildId", gmr.GuildID), + ("UserId", gmr.User.ID) + ), + IInviteCreate ic => PushProperties( + type, + ("GuildId", ic.GuildID), + ("ChannelId", ic.ChannelID), + ("InviteCode", ic.Code) + ), + IInviteDelete id => PushProperties( + type, + ("GuildId", id.GuildID), + ("ChannelId", id.ChannelID), + ("Code", id.Code) + ), + IChannelCreate cc => PushProperties( + type, + ("GuildId", cc.GuildID), + ("ChannelId", cc.ID) + ), + IChannelUpdate cu => PushProperties( + type, + ("GuildId", cu.GuildID), + ("ChannelId", cu.ID) + ), + IChannelDelete cd => PushProperties( + type, + ("GuildId", cd.GuildID), + ("ChannelId", cd.ID) + ), + IGuildAuditLogEntryCreate ale => PushProperties( + type, + ("GuildId", ale.GuildID), + ("AuditLogEntryId", ale.ID), + ("ActionType", ale.ActionType) + ), + IGuildBanAdd gba => PushProperties( + type, + ("GuildId", gba.GuildID), + ("UserId", gba.User.ID) + ), + IGuildBanRemove gbr => PushProperties( + type, + ("GuildId", gbr.GuildID), + ("UserId", gbr.User.ID) + ), + IGuildCreate gc => PushProperties( + type, + ("GuildId", gc.Guild.Match(g => g.ID, g => g.ID)) + ), + IGuildDelete gd => PushProperties(type, ("GuildId", gd.ID)), + IGuildEmojisUpdate geu => PushProperties(type, ("GuildId", geu.GuildID)), + IGuildMembersChunk gmc => PushProperties( + type, + ("GuildId", gmc.GuildID), + ("MemberCount", gmc.Members.Count), + ("ChunkIndex", gmc.ChunkIndex), + ("ChunkCount", gmc.ChunkCount) + ), + IGuildUpdate gu => PushProperties(type, ("GuildId", gu.ID)), _ => PushProperties(type), }; } From 5157105c35f1806955765b295e3cd11025ff87ee Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 27 Nov 2024 16:17:11 +0100 Subject: [PATCH 08/18] fix: better logging - verbose logging for log channel resolving logic - don't LOG TOKENS TO THE CONSOLE OR SEQ - actually log verbose logs to seq - report open db connections to prometheus --- Catalogger.Backend/CataloggerMetrics.cs | 5 ++ .../Database/DatabaseConnection.cs | 15 +--- Catalogger.Backend/Database/DatabasePool.cs | 30 +------ .../Extensions/StartupExtensions.cs | 17 ++-- .../Services/MetricsCollectionService.cs | 1 + .../Services/WebhookExecutorService.cs | 89 ++++++++++++++++++- Catalogger.GoImporter/Program.cs | 2 +- 7 files changed, 109 insertions(+), 50 deletions(-) diff --git a/Catalogger.Backend/CataloggerMetrics.cs b/Catalogger.Backend/CataloggerMetrics.cs index 1dc0cf0..ce55805 100644 --- a/Catalogger.Backend/CataloggerMetrics.cs +++ b/Catalogger.Backend/CataloggerMetrics.cs @@ -29,6 +29,11 @@ public static class CataloggerMetrics public static long MessageRateMinute { get; set; } + public static readonly Gauge DatabaseConnections = Metrics.CreateGauge( + "catalogger_open_database_connections", + "Number of open database connections" + ); + public static readonly Gauge GuildsCached = Metrics.CreateGauge( "catalogger_cache_guilds", "Number of guilds in the cache" diff --git a/Catalogger.Backend/Database/DatabaseConnection.cs b/Catalogger.Backend/Database/DatabaseConnection.cs index 0c44667..f15c2c7 100644 --- a/Catalogger.Backend/Database/DatabaseConnection.cs +++ b/Catalogger.Backend/Database/DatabaseConnection.cs @@ -20,14 +20,9 @@ using Npgsql; namespace Catalogger.Backend.Database; -public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner) - : DbConnection, - IDisposable +public class DatabaseConnection(NpgsqlConnection inner) : DbConnection, IDisposable { - public Guid ConnectionId => id; public NpgsqlConnection Inner => inner; - private readonly ILogger _logger = logger.ForContext(); - private readonly DateTimeOffset _openTime = DateTimeOffset.UtcNow; private bool _hasClosed; @@ -43,8 +38,6 @@ public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner) } DatabasePool.DecrementConnections(); - var openFor = DateTimeOffset.UtcNow - _openTime; - _logger.Verbose("Closing connection {ConnId}, open for {OpenFor}", ConnectionId, openFor); _hasClosed = true; await inner.CloseAsync(); } @@ -52,11 +45,7 @@ public class DatabaseConnection(Guid id, ILogger logger, NpgsqlConnection inner) protected override async ValueTask BeginDbTransactionAsync( IsolationLevel isolationLevel, CancellationToken cancellationToken - ) - { - _logger.Verbose("Beginning transaction on connection {ConnId}", ConnectionId); - return await inner.BeginTransactionAsync(isolationLevel, cancellationToken); - } + ) => await inner.BeginTransactionAsync(isolationLevel, cancellationToken); public new void Dispose() { diff --git a/Catalogger.Backend/Database/DatabasePool.cs b/Catalogger.Backend/Database/DatabasePool.cs index fb266dd..7b23da1 100644 --- a/Catalogger.Backend/Database/DatabasePool.cs +++ b/Catalogger.Backend/Database/DatabasePool.cs @@ -24,18 +24,13 @@ namespace Catalogger.Backend.Database; public class DatabasePool { - private readonly ILogger _rootLogger; - private readonly ILogger _logger; private readonly NpgsqlDataSource _dataSource; private static int _openConnections; public static int OpenConnections => _openConnections; - public DatabasePool(Config config, ILogger logger, ILoggerFactory? loggerFactory) + public DatabasePool(Config config, ILoggerFactory? loggerFactory) { - _rootLogger = logger; - _logger = logger.ForContext(); - var connString = new NpgsqlConnectionStringBuilder(config.Database.Url) { Timeout = config.Database.Timeout ?? 5, @@ -49,27 +44,10 @@ public class DatabasePool _dataSource = dataSourceBuilder.Build(); } - public async Task AcquireAsync(CancellationToken ct = default) - { - return new DatabaseConnection( - LogOpen(), - _rootLogger, - await _dataSource.OpenConnectionAsync(ct) - ); - } + public async Task AcquireAsync(CancellationToken ct = default) => + new(await _dataSource.OpenConnectionAsync(ct)); - public DatabaseConnection Acquire() - { - return new DatabaseConnection(LogOpen(), _rootLogger, _dataSource.OpenConnection()); - } - - private Guid LogOpen() - { - var connId = Guid.NewGuid(); - _logger.Verbose("Opening database connection {ConnId}", connId); - IncrementConnections(); - return connId; - } + public DatabaseConnection Acquire() => new(_dataSource.OpenConnection()); public async Task ExecuteAsync( Func func, diff --git a/Catalogger.Backend/Extensions/StartupExtensions.cs b/Catalogger.Backend/Extensions/StartupExtensions.cs index c1af3ab..f9a226c 100644 --- a/Catalogger.Backend/Extensions/StartupExtensions.cs +++ b/Catalogger.Backend/Extensions/StartupExtensions.cs @@ -51,19 +51,22 @@ public static class StartupExtensions { var logCfg = new LoggerConfiguration() .Enrich.FromLogContext() - .MinimumLevel.Is(config.Logging.LogEventLevel) + .MinimumLevel.Verbose() + // Most Microsoft.* package logs are needlessly verbose, so we restrict them to INFO level and up + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) // ASP.NET's built in request logs are extremely verbose, so we use Serilog's instead. // Serilog doesn't disable the built-in logs, so we do it here. - .MinimumLevel.Override("Microsoft", LogEventLevel.Information) - .MinimumLevel.Override( - "Microsoft.EntityFrameworkCore.Database.Command", - config.Logging.LogQueries ? LogEventLevel.Information : LogEventLevel.Fatal - ) .MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) + // Let's not put webhook tokens and even *full bot tokens* in the logs, thank you + .MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning) // The default theme doesn't support light mode - .WriteTo.Console(theme: AnsiConsoleTheme.Sixteen, applyThemeToRedirectedOutput: true); + .WriteTo.Console( + theme: AnsiConsoleTheme.Sixteen, + applyThemeToRedirectedOutput: true, + restrictedToMinimumLevel: config.Logging.LogEventLevel + ); if (config.Logging.SeqLogUrl != null) { diff --git a/Catalogger.Backend/Services/MetricsCollectionService.cs b/Catalogger.Backend/Services/MetricsCollectionService.cs index 5c003a0..afba829 100644 --- a/Catalogger.Backend/Services/MetricsCollectionService.cs +++ b/Catalogger.Backend/Services/MetricsCollectionService.cs @@ -43,6 +43,7 @@ public class MetricsCollectionService( var messageCount = await conn.ExecuteScalarAsync("select count(id) from messages"); + CataloggerMetrics.DatabaseConnections.Set(DatabasePool.OpenConnections); CataloggerMetrics.GuildsCached.Set(guildCache.Size); CataloggerMetrics.ChannelsCached.Set(channelCache.Size); CataloggerMetrics.RolesCached.Set(roleCache.Size); diff --git a/Catalogger.Backend/Services/WebhookExecutorService.cs b/Catalogger.Backend/Services/WebhookExecutorService.cs index f82ea75..78f12f5 100644 --- a/Catalogger.Backend/Services/WebhookExecutorService.cs +++ b/Catalogger.Backend/Services/WebhookExecutorService.cs @@ -315,6 +315,13 @@ public class WebhookExecutorService( Snowflake channelId ) { + _logger.Verbose( + "Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}", + logChannelType, + guild.Id, + channelId + ); + if (!channelCache.TryGet(channelId, out var channel)) return GetDefaultLogChannel(guild, logChannelType); @@ -329,7 +336,13 @@ public class WebhookExecutorService( // parent_id should always have a value for threads channelId = channel.ParentID.Value!.Value; if (!channelCache.TryGet(channelId, out var parentChannel)) + { + _logger.Verbose( + "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", + channelId + ); return GetDefaultLogChannel(guild, logChannelType); + } categoryId = parentChannel.ParentID.Value; } else @@ -343,8 +356,16 @@ public class WebhookExecutorService( guild.IgnoredChannels.Contains(channelId.Value) || (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value)) ) + { + _logger.Verbose( + "Channel {ChannelId} or its parent {CategoryId} is ignored", + channelId, + categoryId + ); return null; + } + _logger.Verbose("Returning default log channel for {EventType}", logChannelType); return GetDefaultLogChannel(guild, logChannelType); } @@ -355,13 +376,29 @@ public class WebhookExecutorService( ulong? userId = null ) { + _logger.Verbose( + "Getting log channel for event {Event}. Channel ID: {ChannelId}, user ID: {UserId}", + logChannelType, + channelId, + userId + ); + // Check if the user is ignored globally if (userId != null && guild.Messages.IgnoredUsers.Contains(userId.Value)) + { + _logger.Verbose("User {UserId} is ignored globally", userId); return null; + } // If the user isn't ignored and we didn't get a channel ID, return the default log channel if (channelId == null) + { + _logger.Verbose( + "No channel ID given so returning default channel for {Event}", + logChannelType + ); return GetDefaultLogChannel(guild, logChannelType); + } if (!channelCache.TryGet(channelId.Value, out var channel)) return null; @@ -377,7 +414,13 @@ public class WebhookExecutorService( // parent_id should always have a value for threads channelId = channel.ParentID.Value!.Value; if (!channelCache.TryGet(channelId.Value, out var parentChannel)) + { + _logger.Verbose( + "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", + channelId + ); return GetDefaultLogChannel(guild, logChannelType); + } categoryId = parentChannel.ParentID.Value; } else @@ -391,7 +434,14 @@ public class WebhookExecutorService( guild.Messages.IgnoredChannels.Contains(channelId.Value.Value) || categoryId != null && guild.Messages.IgnoredChannels.Contains(categoryId.Value.Value) ) + { + _logger.Verbose( + "Channel {ChannelId} or its parent {CategoryId} is ignored", + channelId, + categoryId + ); return null; + } if (userId != null) { @@ -408,13 +458,27 @@ public class WebhookExecutorService( : [] ) ?? []; if (channelIgnoredUsers.Concat(categoryIgnoredUsers).Contains(userId.Value)) + { + _logger.Verbose( + "User {UserId} is ignored in {ChannelId} or its category {CategoryId}", + userId, + channelId, + categoryId + ); return null; + } } // These three events can be redirected to other channels. Redirects can be on a channel or category level. // The events are only redirected if they're supposed to be logged in the first place. if (GetDefaultLogChannel(guild, logChannelType) == 0) + { + _logger.Verbose( + "No default log channel for event {EventType}, ignoring event", + logChannelType + ); return null; + } var categoryRedirect = categoryId != null @@ -422,10 +486,29 @@ public class WebhookExecutorService( : 0; if (guild.Channels.Redirects.TryGetValue(channelId.Value.Value, out var channelRedirect)) + { + _logger.Verbose( + "Messages from channel {ChannelId} should be redirected to {RedirectId}", + channelId, + channelRedirect + ); return channelRedirect; - return categoryRedirect != 0 - ? categoryRedirect - : GetDefaultLogChannel(guild, logChannelType); + } + + if (categoryRedirect != 0) + { + _logger.Verbose( + "Messages from categoryId {CategoryId} should be redirected to {RedirectId}", + categoryId, + categoryRedirect + ); + } + + _logger.Verbose( + "No redirects or ignores for event {EventType}, returning default log channel", + logChannelType + ); + return GetDefaultLogChannel(guild, logChannelType); } public static ulong GetDefaultLogChannel(Guild guild, LogChannelType logChannelType) => diff --git a/Catalogger.GoImporter/Program.cs b/Catalogger.GoImporter/Program.cs index a34f08a..3746ef4 100644 --- a/Catalogger.GoImporter/Program.cs +++ b/Catalogger.GoImporter/Program.cs @@ -43,7 +43,7 @@ internal class Program return; } - var db = new DatabasePool(config, Log.Logger, null); + var db = new DatabasePool(config, null); DatabasePool.ConfigureDapper(); if (Environment.GetEnvironmentVariable("MIGRATE") == "true") { From 27e1903c4b2baccde901303edfa558b804440508 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 10 Dec 2024 02:34:45 +0100 Subject: [PATCH 09/18] fix: also increment database connection count --- Catalogger.Backend/Database/DatabasePool.cs | 13 ++++++++++--- catalogger.sln.DotSettings | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Catalogger.Backend/Database/DatabasePool.cs b/Catalogger.Backend/Database/DatabasePool.cs index 7b23da1..29a845e 100644 --- a/Catalogger.Backend/Database/DatabasePool.cs +++ b/Catalogger.Backend/Database/DatabasePool.cs @@ -44,10 +44,17 @@ public class DatabasePool _dataSource = dataSourceBuilder.Build(); } - public async Task AcquireAsync(CancellationToken ct = default) => - new(await _dataSource.OpenConnectionAsync(ct)); + public async Task AcquireAsync(CancellationToken ct = default) + { + IncrementConnections(); + return new DatabaseConnection(await _dataSource.OpenConnectionAsync(ct)); + } - public DatabaseConnection Acquire() => new(_dataSource.OpenConnection()); + public DatabaseConnection Acquire() + { + IncrementConnections(); + return new DatabaseConnection(_dataSource.OpenConnection()); + } public async Task ExecuteAsync( Func func, diff --git a/catalogger.sln.DotSettings b/catalogger.sln.DotSettings index b3d1d34..67d3f0f 100644 --- a/catalogger.sln.DotSettings +++ b/catalogger.sln.DotSettings @@ -1,3 +1,5 @@  + UseVar + UseVar True True \ No newline at end of file From 0d7e809ef63026b18ada93ed271c1f2aad7f7381 Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 13 Dec 2024 14:39:51 +0100 Subject: [PATCH 10/18] fix: fall back to default log channel if the queried channel isn't in cache --- .../Services/WebhookExecutorService.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Catalogger.Backend/Services/WebhookExecutorService.cs b/Catalogger.Backend/Services/WebhookExecutorService.cs index 78f12f5..6b73cb4 100644 --- a/Catalogger.Backend/Services/WebhookExecutorService.cs +++ b/Catalogger.Backend/Services/WebhookExecutorService.cs @@ -323,7 +323,13 @@ public class WebhookExecutorService( ); if (!channelCache.TryGet(channelId, out var channel)) + { + _logger.Verbose( + "Channel with ID {ChannelId} is not cached, returning default log channel", + channelId + ); return GetDefaultLogChannel(guild, logChannelType); + } Snowflake? categoryId; if ( @@ -401,7 +407,13 @@ public class WebhookExecutorService( } if (!channelCache.TryGet(channelId.Value, out var channel)) - return null; + { + _logger.Verbose( + "Channel with ID {ChannelId} is not cached, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); + } Snowflake? categoryId; if ( From 1a63540f89f18d6216ac17fd0c4569d5e904fe30 Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 5 Jan 2025 13:57:57 -0500 Subject: [PATCH 11/18] fix: don't register subcommand groups separately --- Catalogger.Backend/Program.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Catalogger.Backend/Program.cs b/Catalogger.Backend/Program.cs index 7f948fc..a7a4a1b 100644 --- a/Catalogger.Backend/Program.cs +++ b/Catalogger.Backend/Program.cs @@ -97,12 +97,7 @@ builder .WithCommandGroup() .WithCommandGroup() .WithCommandGroup() - .WithCommandGroup() - .WithCommandGroup() - .WithCommandGroup() .WithCommandGroup() - .WithCommandGroup() - .WithCommandGroup() .WithCommandGroup() .WithCommandGroup() // End command tree From db3e6fa7b060aeaf40bbbcae4c571caaf8fa15b4 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 11 Feb 2025 15:28:12 +0100 Subject: [PATCH 12/18] fix: try to fix database connections not being closed sometimes --- .../Database/DatabaseConnection.cs | 20 ++++++++++++++----- Catalogger.Backend/Services/TimeoutService.cs | 9 ++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Catalogger.Backend/Database/DatabaseConnection.cs b/Catalogger.Backend/Database/DatabaseConnection.cs index f15c2c7..b2488f9 100644 --- a/Catalogger.Backend/Database/DatabaseConnection.cs +++ b/Catalogger.Backend/Database/DatabaseConnection.cs @@ -17,6 +17,7 @@ using System.Data; using System.Data.Common; using System.Diagnostics.CodeAnalysis; using Npgsql; +using Serilog; namespace Catalogger.Backend.Database; @@ -49,11 +50,18 @@ public class DatabaseConnection(NpgsqlConnection inner) : DbConnection, IDisposa public new void Dispose() { - Close(); - inner.Dispose(); + Dispose(true); GC.SuppressFinalize(this); } + protected override void Dispose(bool disposing) + { + Log.Error("Called Dispose method on DbConnection, should call DisposeAsync!"); + Log.Warning("CloseAsync will be called synchronously."); + CloseAsync().Wait(); + inner.Dispose(); + } + public override async ValueTask DisposeAsync() { await CloseAsync(); @@ -62,13 +70,13 @@ public class DatabaseConnection(NpgsqlConnection inner) : DbConnection, IDisposa } protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => - inner.BeginTransaction(isolationLevel); + throw new SyncException(nameof(BeginDbTransaction)); public override void ChangeDatabase(string databaseName) => inner.ChangeDatabase(databaseName); - public override void Close() => inner.Close(); + public override void Close() => throw new SyncException(nameof(Close)); - public override void Open() => inner.Open(); + public override void Open() => throw new SyncException(nameof(Open)); [AllowNull] public override string ConnectionString @@ -83,4 +91,6 @@ public class DatabaseConnection(NpgsqlConnection inner) : DbConnection, IDisposa public override string ServerVersion => inner.ServerVersion; protected override DbCommand CreateDbCommand() => inner.CreateCommand(); + + public class SyncException(string method) : Exception($"Tried to use sync method {method}"); } diff --git a/Catalogger.Backend/Services/TimeoutService.cs b/Catalogger.Backend/Services/TimeoutService.cs index 6615f43..03d1831 100644 --- a/Catalogger.Backend/Services/TimeoutService.cs +++ b/Catalogger.Backend/Services/TimeoutService.cs @@ -25,7 +25,8 @@ public class TimeoutService( _logger.Information("Populating timeout service with existing database timeouts"); await using var scope = serviceProvider.CreateAsyncScope(); - var timeoutRepository = scope.ServiceProvider.GetRequiredService(); + await using var timeoutRepository = + scope.ServiceProvider.GetRequiredService(); var timeouts = await timeoutRepository.GetAllAsync(); foreach (var timeout in timeouts) @@ -53,8 +54,10 @@ public class TimeoutService( _logger.Information("Sending timeout log for {TimeoutId}", timeoutId); await using var scope = serviceProvider.CreateAsyncScope(); - var guildRepository = scope.ServiceProvider.GetRequiredService(); - var timeoutRepository = scope.ServiceProvider.GetRequiredService(); + await using var guildRepository = + scope.ServiceProvider.GetRequiredService(); + await using var timeoutRepository = + scope.ServiceProvider.GetRequiredService(); var timeout = await timeoutRepository.RemoveAsync(timeoutId); if (timeout == null) From cb43ac1a503fbb30a4cdb29cc2f18dba133ed24d Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 14 Mar 2025 20:11:00 +0100 Subject: [PATCH 13/18] chore(backend): update dependencies --- .config/dotnet-tools.json | 4 +- .../Messages/MessageCreateResponder.cs | 2 +- .../Messages/MessageUpdateResponder.cs | 51 ++----------------- .../Cache/RedisCache/RedisMemberCache.cs | 1 + Catalogger.Backend/Catalogger.Backend.csproj | 6 +-- .../Repositories/MessageRepository.cs | 11 +++- Catalogger.Backend/Program.cs | 3 +- README.md | 7 --- 8 files changed, 21 insertions(+), 64 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 085fcbc..d2b1b96 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,14 +3,14 @@ "isRoot": true, "tools": { "csharpier": { - "version": "0.30.1", + "version": "0.30.6", "commands": [ "dotnet-csharpier" ], "rollForward": false }, "husky": { - "version": "0.7.1", + "version": "0.7.2", "commands": [ "husky" ], diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs index 5098cd2..0f70043 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs @@ -77,7 +77,7 @@ public class MessageCreateResponder( return Result.Success; } - await messageRepository.SaveMessageAsync(msg, ct); + await messageRepository.SaveMessageAsync(msg, msg.GuildID, ct); return Result.Success; } } diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs index fa1e75d..0bf2c28 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageUpdateResponder.cs @@ -20,7 +20,6 @@ using Catalogger.Backend.Services; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; -using Remora.Discord.API.Gateway.Events; using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; @@ -40,13 +39,9 @@ public class MessageUpdateResponder( { private readonly ILogger _logger = logger.ForContext(); - public async Task RespondAsync(IMessageUpdate evt, CancellationToken ct = default) + public async Task RespondAsync(IMessageUpdate msg, CancellationToken ct = default) { - using var _ = LogUtils.Enrich(evt); - - // Discord only *very* recently changed message update events to have all fields, - // so we convert the event to a MessageCreate to avoid having to unwrap every single field - var msg = ConvertToMessageCreate(evt); + using var _ = LogUtils.Enrich(msg); if (!msg.GuildID.IsDefined()) { @@ -134,7 +129,7 @@ public class MessageUpdateResponder( if (oldMessage is { System: not null, Member: not null }) { embedBuilder.WithTitle($"Message by {msg.Author.Username} edited"); - embedBuilder.AddField("\u200b", "**PluralKit information**", false); + embedBuilder.AddField("\u200b", "**PluralKit information**"); embedBuilder.AddField("System ID", oldMessage.System, true); embedBuilder.AddField("Member ID", oldMessage.Member, true); } @@ -174,7 +169,7 @@ public class MessageUpdateResponder( ) { if ( - !await messageRepository.SaveMessageAsync(msg, ct) + !await messageRepository.SaveMessageAsync(msg, msg.GuildID, ct) && msg.ApplicationID.Is(DiscordUtils.PkUserId) ) { @@ -196,44 +191,6 @@ public class MessageUpdateResponder( } } - private static MessageCreate ConvertToMessageCreate(IMessageUpdate evt) => - new( - evt.GuildID, - evt.Member, - evt.Mentions.GetOrThrow(), - evt.ID.GetOrThrow(), - evt.ChannelID.GetOrThrow(), - evt.Author.GetOrThrow(), - evt.Content.GetOrThrow(), - evt.Timestamp.GetOrThrow(), - evt.EditedTimestamp.GetOrThrow(), - IsTTS: false, - evt.MentionsEveryone.GetOrThrow(), - evt.MentionedRoles.GetOrThrow(), - evt.MentionedChannels, - evt.Attachments.GetOrThrow(), - evt.Embeds.GetOrThrow(), - evt.Reactions, - evt.Nonce, - evt.IsPinned.GetOrThrow(), - evt.WebhookID, - evt.Type.GetOrThrow(), - evt.Activity, - evt.Application, - evt.ApplicationID, - evt.MessageReference, - evt.Flags, - evt.ReferencedMessage, - evt.Interaction, - evt.Thread, - evt.Components, - evt.StickerItems, - evt.Position, - evt.Resolved, - evt.InteractionMetadata, - evt.Poll - ); - private static IEnumerable ChunksUpTo(string str, int maxChunkSize) { for (var i = 0; i < str.Length; i += maxChunkSize) diff --git a/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs b/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs index dfa9694..0e08c1f 100644 --- a/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs +++ b/Catalogger.Backend/Cache/RedisCache/RedisMemberCache.cs @@ -234,6 +234,7 @@ internal record RedisMember( User.ToRemoraUser(), Nickname, Avatar != null ? new ImageHash(Avatar) : null, + Banner: null, Roles.Select(DiscordSnowflake.New).ToList(), JoinedAt, PremiumSince, diff --git a/Catalogger.Backend/Catalogger.Backend.csproj b/Catalogger.Backend/Catalogger.Backend.csproj index 6d048ab..b377418 100644 --- a/Catalogger.Backend/Catalogger.Backend.csproj +++ b/Catalogger.Backend/Catalogger.Backend.csproj @@ -21,18 +21,18 @@ - + - + - + diff --git a/Catalogger.Backend/Database/Repositories/MessageRepository.cs b/Catalogger.Backend/Database/Repositories/MessageRepository.cs index 17925c3..fc45e4f 100644 --- a/Catalogger.Backend/Database/Repositories/MessageRepository.cs +++ b/Catalogger.Backend/Database/Repositories/MessageRepository.cs @@ -18,6 +18,7 @@ using Catalogger.Backend.Extensions; using Dapper; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Objects; using Remora.Rest.Core; namespace Catalogger.Backend.Database.Repositories; @@ -63,7 +64,11 @@ public class MessageRepository( /// /// Adds a new message. If the message is already in the database, updates the existing message instead. /// - public async Task SaveMessageAsync(IMessageCreate msg, CancellationToken ct = default) + public async Task SaveMessageAsync( + IMessage msg, + Optional guildId, + CancellationToken ct = default + ) { var content = await Task.Run( () => @@ -107,7 +112,9 @@ public class MessageRepository( Id = msg.ID.Value, UserId = msg.Author.ID.Value, ChannelId = msg.ChannelID.Value, - GuildId = msg.GuildID.Map(s => s.Value).OrDefault(), + GuildId = guildId.IsDefined(out var guildIdValue) + ? guildIdValue.Value + : (ulong?)null, Content = content, Username = username, Metadata = metadata, diff --git a/Catalogger.Backend/Program.cs b/Catalogger.Backend/Program.cs index a7a4a1b..6fbf6b3 100644 --- a/Catalogger.Backend/Program.cs +++ b/Catalogger.Backend/Program.cs @@ -66,8 +66,7 @@ builder | GatewayIntents.GuildMessages | GatewayIntents.GuildWebhooks | GatewayIntents.MessageContents - // Actually GUILD_EXPRESSIONS - | GatewayIntents.GuildEmojisAndStickers; + | GatewayIntents.GuildExpressions; // Set a default status for all shards. This is updated to a shard-specific one in StatusUpdateService. g.Presence = new UpdatePresence( diff --git a/README.md b/README.md index 9b9603c..e33918a 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,6 @@ Command-line tools for this project can be installed with `dotnet tool restore`. - We use [CSharpier][csharpier] for formatting .NET code. It can be called with `dotnet csharpier .`, but is automatically run by Husky pre-commit. -### Nuget - -We currently use Remora's GitHub packages as the releases on nuget.org are missing some key features. -Add these with `dotnet nuget add source --username --password --store-password-in-clear-text --name Remora "https://nuget.pkg.github.com/Remora/index.json"` - -You must generate a personal access token (classic) [here](personal-access-token). Only give it the `read:packages` permission. - ## Deploying Catalogger yourself The bot itself should run on any server with .NET 8 and PostgreSQL 15 or later. From 84c3b42874ee89e8b5389088ddcc70cf38f9f5d0 Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 14 Mar 2025 20:29:01 +0100 Subject: [PATCH 14/18] fix(backend): ignore proxied messages if the original is ignored --- .../Messages/MessageCreateResponder.cs | 26 +++++++++++++++++++ .../Messages/MessageDeleteResponder.cs | 9 +++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs index 0f70043..e54d12b 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageCreateResponder.cs @@ -144,6 +144,19 @@ public partial class PkMessageHandler(ILogger logger, IServiceProvider services) await using var messageRepository = scope.ServiceProvider.GetRequiredService(); + if (await messageRepository.IsMessageIgnoredAsync(originalId)) + { + _logger.Debug( + "Proxied message {MessageId} should be ignored as trigger {OriginalId} is already ignored", + msgId, + originalId + ); + + await messageRepository.IgnoreMessageAsync(originalId); + await messageRepository.IgnoreMessageAsync(msgId); + return; + } + _logger.Debug( "Setting proxy data for {MessageId} and ignoring {OriginalId}", msgId, @@ -195,6 +208,19 @@ public partial class PkMessageHandler(ILogger logger, IServiceProvider services) pkMessage.Original ); + if (await messageRepository.IsMessageIgnoredAsync(pkMessage.Original)) + { + _logger.Debug( + "Proxied message {MessageId} should be ignored as trigger {OriginalId} is already ignored", + pkMessage.Id, + pkMessage.Original + ); + + await messageRepository.IgnoreMessageAsync(pkMessage.Original); + await messageRepository.IgnoreMessageAsync(msgId); + return; + } + await messageRepository.SetProxiedMessageDataAsync( msgId, pkMessage.Original, diff --git a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs index 315daec..55a5a68 100644 --- a/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs +++ b/Catalogger.Backend/Bot/Responders/Messages/MessageDeleteResponder.cs @@ -18,7 +18,6 @@ using Catalogger.Backend.Database.Repositories; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Humanizer; -using NodaTime; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Objects; @@ -27,7 +26,6 @@ using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Rest.Core; using Remora.Results; -using Serilog.Context; namespace Catalogger.Backend.Bot.Responders.Messages; @@ -38,7 +36,6 @@ public class MessageDeleteResponder( WebhookExecutorService webhookExecutor, ChannelCache channelCache, UserCache userCache, - IClock clock, PluralkitApiService pluralkitApi ) : IResponder { @@ -81,8 +78,8 @@ public class MessageDeleteResponder( new Embed( Title: "Message deleted", Description: $"A message not found in the database was deleted in <#{evt.ChannelID}> ({evt.ChannelID}).", - Footer: new EmbedFooter(Text: $"ID: {evt.ID}"), - Timestamp: clock.GetCurrentInstant().ToDateTimeOffset() + Footer: new EmbedFooter(Text: $"ID: {evt.ID} | Original sent at"), + Timestamp: evt.ID.Timestamp ) ); @@ -124,7 +121,7 @@ public class MessageDeleteResponder( .WithTitle("Message deleted") .WithDescription(msg.Content) .WithColour(DiscordUtils.Red) - .WithFooter($"ID: {msg.Id}") + .WithFooter($"ID: {msg.Id} | Original sent at") .WithTimestamp(evt.ID); if (user != null) From 8a4e3ff184d545f6113eebb605e13211efcd0058 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 20 Mar 2025 15:24:23 +0100 Subject: [PATCH 15/18] refactor: return CataloggerError as Results instead of throwing in commands --- .../Bot/Commands/ChannelCommands.cs | 20 ++++++++-- .../Bot/Commands/ChannelCommandsComponents.cs | 38 +++++++++---------- .../Bot/Commands/IgnoreEntitiesCommands.cs | 6 +-- .../IgnoreMessageCommands.Channels.cs | 4 +- .../Commands/IgnoreMessageCommands.Roles.cs | 2 +- .../Commands/IgnoreMessageCommands.Users.cs | 2 +- .../Bot/Commands/InviteCommands.cs | 2 +- .../Bot/Commands/KeyRoleCommands.cs | 6 +-- .../Bot/Commands/RedirectCommands.cs | 2 +- .../Bot/Commands/WatchlistCommands.cs | 2 +- Catalogger.Backend/CataloggerError.cs | 9 ++++- 11 files changed, 57 insertions(+), 36 deletions(-) diff --git a/Catalogger.Backend/Bot/Commands/ChannelCommands.cs b/Catalogger.Backend/Bot/Commands/ChannelCommands.cs index 2dab68f..7f2ec0d 100644 --- a/Catalogger.Backend/Bot/Commands/ChannelCommands.cs +++ b/Catalogger.Backend/Bot/Commands/ChannelCommands.cs @@ -43,6 +43,7 @@ public class ChannelCommands( Config config, GuildRepository guildRepository, GuildCache guildCache, + GuildFetchService guildFetchService, ChannelCache channelCache, IMemberCache memberCache, IFeedbackService feedbackService, @@ -68,8 +69,11 @@ public class ChannelCommands( public async Task CheckPermissionsAsync() { var (userId, guildId) = contextInjection.GetUserAndGuild(); + if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + { + return CataloggerError.Result($"Guild {guildId} not in cache"); + } var embed = new EmbedBuilder().WithTitle($"Permission check for {guild.Name}"); @@ -78,8 +82,18 @@ public class ChannelCommands( DiscordSnowflake.New(config.Discord.ApplicationId) ); var currentUser = await memberCache.TryGetAsync(guildId, userId); + if (botUser == null || currentUser == null) - throw new CataloggerError("Bot member or invoking member not found in cache"); + { + // If this happens, something has gone wrong when fetching members. Refetch the guild's members. + guildFetchService.EnqueueGuild(guildId); + _logger.Error( + "Either our own user {BotId} or the invoking user {UserId} is not in cache, aborting permission check", + config.Discord.ApplicationId, + userId + ); + return CataloggerError.Result("Bot member or invoking member not found in cache"); + } // We don't want to check categories or threads var guildChannels = channelCache @@ -204,7 +218,7 @@ public class ChannelCommands( { var (userId, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildChannels = channelCache.GuildChannels(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); diff --git a/Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs b/Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs index 9867e7d..1104c82 100644 --- a/Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs +++ b/Catalogger.Backend/Bot/Commands/ChannelCommandsComponents.cs @@ -50,15 +50,15 @@ public class ChannelCommandsComponents( public async Task OnMenuSelectionAsync(IReadOnlyList values) { if (contextInjection.Context is not IInteractionCommandContext ctx) - throw new CataloggerError("No context"); + return CataloggerError.Result("No context"); if (!ctx.TryGetUserID(out var userId)) - throw new CataloggerError("No user ID in context"); + return CataloggerError.Result("No user ID in context"); if (!ctx.Interaction.Message.TryGet(out var msg)) - throw new CataloggerError("No message ID in context"); + return CataloggerError.Result("No message ID in context"); if (!ctx.TryGetGuildID(out var guildId)) - throw new CataloggerError("No guild ID in context"); + return CataloggerError.Result("No guild ID in context"); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildChannels = channelCache.GuildChannels(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); @@ -76,7 +76,7 @@ public class ChannelCommandsComponents( var state = values[0]; if (!Enum.TryParse(state, out var logChannelType)) - throw new CataloggerError($"Invalid config-channels state {state}"); + return CataloggerError.Result($"Invalid config-channels state {state}"); var channelId = WebhookExecutorService.GetDefaultLogChannel(guildConfig, logChannelType); string? channelMention; @@ -147,15 +147,15 @@ public class ChannelCommandsComponents( public async Task OnButtonPressedAsync(string state) { if (contextInjection.Context is not IInteractionCommandContext ctx) - throw new CataloggerError("No context"); + return CataloggerError.Result("No context"); if (!ctx.TryGetUserID(out var userId)) - throw new CataloggerError("No user ID in context"); + return CataloggerError.Result("No user ID in context"); if (!ctx.Interaction.Message.TryGet(out var msg)) - throw new CataloggerError("No message ID in context"); + return CataloggerError.Result("No message ID in context"); if (!ctx.TryGetGuildID(out var guildId)) - throw new CataloggerError("No guild ID in context"); + return CataloggerError.Result("No guild ID in context"); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildChannels = channelCache.GuildChannels(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); @@ -179,9 +179,9 @@ public class ChannelCommandsComponents( ); case "reset": if (lease.Data.CurrentPage == null) - throw new CataloggerError("CurrentPage was null in reset button callback"); + return CataloggerError.Result("CurrentPage was null in reset button callback"); if (!Enum.TryParse(lease.Data.CurrentPage, out var channelType)) - throw new CataloggerError( + return CataloggerError.Result( $"Invalid config-channels CurrentPage: '{lease.Data.CurrentPage}'" ); @@ -281,15 +281,15 @@ public class ChannelCommandsComponents( public async Task OnMenuSelectionAsync(IReadOnlyList channels) { if (contextInjection.Context is not IInteractionCommandContext ctx) - throw new CataloggerError("No context"); + return CataloggerError.Result("No context"); if (!ctx.TryGetUserID(out var userId)) - throw new CataloggerError("No user ID in context"); + return CataloggerError.Result("No user ID in context"); if (!ctx.Interaction.Message.TryGet(out var msg)) - throw new CataloggerError("No message ID in context"); + return CataloggerError.Result("No message ID in context"); if (!ctx.TryGetGuildID(out var guildId)) - throw new CataloggerError("No guild ID in context"); + return CataloggerError.Result("No guild ID in context"); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildConfig = await guildRepository.GetAsync(guildId); var channelId = channels[0].ID.ToUlong(); @@ -305,7 +305,7 @@ public class ChannelCommandsComponents( } if (!Enum.TryParse(lease.Data.CurrentPage, out var channelType)) - throw new CataloggerError( + return CataloggerError.Result( $"Invalid config-channels CurrentPage '{lease.Data.CurrentPage}'" ); diff --git a/Catalogger.Backend/Bot/Commands/IgnoreEntitiesCommands.cs b/Catalogger.Backend/Bot/Commands/IgnoreEntitiesCommands.cs index 5ffc7a9..7e8987c 100644 --- a/Catalogger.Backend/Bot/Commands/IgnoreEntitiesCommands.cs +++ b/Catalogger.Backend/Bot/Commands/IgnoreEntitiesCommands.cs @@ -98,7 +98,7 @@ public class IgnoreEntitiesCommands : CommandGroup { var (_, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildConfig = await guildRepository.GetAsync(guildId); @@ -201,14 +201,14 @@ public class IgnoreEntitiesCommands : CommandGroup { var (userId, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildChannels = channelCache.GuildChannels(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); var member = await memberCache.TryGetAsync(guildId, userId); if (member == null) - throw new CataloggerError("Executing member not found"); + return CataloggerError.Result("Executing member not found"); var ignoredChannels = guildConfig .IgnoredChannels.Select(id => diff --git a/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Channels.cs b/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Channels.cs index 69b225e..b61fabc 100644 --- a/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Channels.cs +++ b/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Channels.cs @@ -110,14 +110,14 @@ public partial class IgnoreMessageCommands : CommandGroup { var (userId, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildChannels = channelCache.GuildChannels(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); var member = await memberCache.TryGetAsync(guildId, userId); if (member == null) - throw new CataloggerError("Executing member not found"); + return CataloggerError.Result("Executing member not found"); var ignoredChannels = guildConfig .Messages.IgnoredChannels.Select(id => diff --git a/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Roles.cs b/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Roles.cs index cf3cb10..2cc46b7 100644 --- a/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Roles.cs +++ b/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Roles.cs @@ -90,7 +90,7 @@ public partial class IgnoreMessageCommands { var (_, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildConfig = await guildRepository.GetAsync(guildId); diff --git a/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Users.cs b/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Users.cs index d89f487..15ae280 100644 --- a/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Users.cs +++ b/Catalogger.Backend/Bot/Commands/IgnoreMessageCommands.Users.cs @@ -94,7 +94,7 @@ public partial class IgnoreMessageCommands { var (userId, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild was not cached"); + return CataloggerError.Result("Guild not in cache"); var guildConfig = await guildRepository.GetAsync(guildId); diff --git a/Catalogger.Backend/Bot/Commands/InviteCommands.cs b/Catalogger.Backend/Bot/Commands/InviteCommands.cs index 7ffa031..6ec6991 100644 --- a/Catalogger.Backend/Bot/Commands/InviteCommands.cs +++ b/Catalogger.Backend/Bot/Commands/InviteCommands.cs @@ -59,7 +59,7 @@ public class InviteCommands( var (userId, guildId) = contextInjection.GetUserAndGuild(); var guildInvites = await guildApi.GetGuildInvitesAsync(guildId).GetOrThrow(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var dbInvites = await inviteRepository.GetGuildInvitesAsync(guildId); diff --git a/Catalogger.Backend/Bot/Commands/KeyRoleCommands.cs b/Catalogger.Backend/Bot/Commands/KeyRoleCommands.cs index 1ebc458..dd2ff90 100644 --- a/Catalogger.Backend/Bot/Commands/KeyRoleCommands.cs +++ b/Catalogger.Backend/Bot/Commands/KeyRoleCommands.cs @@ -45,7 +45,7 @@ public class KeyRoleCommands( { var (_, guildId) = contextInjection.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild not in cache"); + return CataloggerError.Result("Guild not in cache"); var guildRoles = roleCache.GuildRoles(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); @@ -85,7 +85,7 @@ public class KeyRoleCommands( var (_, guildId) = contextInjection.GetUserAndGuild(); var role = roleCache.GuildRoles(guildId).FirstOrDefault(r => r.ID == roleId); if (role == null) - throw new CataloggerError("Role is not cached"); + return CataloggerError.Result("Role is not cached"); var guildConfig = await guildRepository.GetAsync(guildId); if (guildConfig.KeyRoles.Any(id => role.ID.Value == id)) @@ -111,7 +111,7 @@ public class KeyRoleCommands( var (_, guildId) = contextInjection.GetUserAndGuild(); var role = roleCache.GuildRoles(guildId).FirstOrDefault(r => r.ID == roleId); if (role == null) - throw new CataloggerError("Role is not cached"); + return CataloggerError.Result("Role is not cached"); var guildConfig = await guildRepository.GetAsync(guildId); if (guildConfig.KeyRoles.All(id => role.ID != id)) diff --git a/Catalogger.Backend/Bot/Commands/RedirectCommands.cs b/Catalogger.Backend/Bot/Commands/RedirectCommands.cs index 586deb3..3864c54 100644 --- a/Catalogger.Backend/Bot/Commands/RedirectCommands.cs +++ b/Catalogger.Backend/Bot/Commands/RedirectCommands.cs @@ -141,7 +141,7 @@ public class RedirectCommands( { var (userId, guildId) = contextInjectionService.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild was not cached"); + return CataloggerError.Result("Guild not in cache"); var guildChannels = channelCache.GuildChannels(guildId).ToList(); var guildConfig = await guildRepository.GetAsync(guildId); diff --git a/Catalogger.Backend/Bot/Commands/WatchlistCommands.cs b/Catalogger.Backend/Bot/Commands/WatchlistCommands.cs index b47a583..c92a886 100644 --- a/Catalogger.Backend/Bot/Commands/WatchlistCommands.cs +++ b/Catalogger.Backend/Bot/Commands/WatchlistCommands.cs @@ -83,7 +83,7 @@ public class WatchlistCommands( { var (userId, guildId) = contextInjectionService.GetUserAndGuild(); if (!guildCache.TryGet(guildId, out var guild)) - throw new CataloggerError("Guild was not cached"); + return CataloggerError.Result("Guild not in cache"); var watchlist = await watchlistRepository.GetGuildWatchlistAsync(guildId); if (watchlist.Count == 0) diff --git a/Catalogger.Backend/CataloggerError.cs b/Catalogger.Backend/CataloggerError.cs index 31abf6f..40322a4 100644 --- a/Catalogger.Backend/CataloggerError.cs +++ b/Catalogger.Backend/CataloggerError.cs @@ -13,6 +13,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using Remora.Results; +using RemoraResult = Remora.Results.Result; + namespace Catalogger.Backend; -public class CataloggerError(string message) : Exception(message) { } +public class CataloggerError(string message) : Exception(message), IResultError +{ + public static RemoraResult Result(string message) => + RemoraResult.FromError(new CataloggerError(message)); +} From 24f6aee57d15ba0ef1a7a5575f6f0b8b1a7fe55b Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 20 Mar 2025 15:24:46 +0100 Subject: [PATCH 16/18] feat: handle CataloggerError results in interaction responder --- .../Responders/CustomInteractionResponder.cs | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs b/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs index c07dc6d..1fd3b9e 100644 --- a/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs +++ b/Catalogger.Backend/Bot/Responders/CustomInteractionResponder.cs @@ -19,17 +19,21 @@ using Remora.Commands.Services; using Remora.Commands.Tokenization; using Remora.Commands.Trees; using Remora.Discord.API.Abstractions.Gateway.Events; +using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; +using Remora.Discord.API.Objects; using Remora.Discord.Commands.Responders; using Remora.Discord.Commands.Services; using Remora.Discord.Gateway.Responders; +using Remora.Rest.Core; using Remora.Results; using Serilog.Context; namespace Catalogger.Backend.Bot.Responders; /// -/// Wrapper for Remora.Discord's default interaction responder, that ignores all events if test mode is enabled. +/// Wrapper for Remora.Discord's default interaction responder, that ignores all events if test mode is enabled, +/// and handles results returned by commands. /// public class CustomInteractionResponder( Config config, @@ -87,6 +91,38 @@ public class CustomInteractionResponder( true ); - return await _inner.RespondAsync(evt, ct); + var result = await _inner.RespondAsync(evt, ct); + if (result.Error is not CataloggerError cataloggerError) + return result; + + return await interactionAPI.CreateInteractionResponseAsync( + evt.ID, + evt.Token, + new InteractionResponse( + Type: InteractionCallbackType.ChannelMessageWithSource, + Data: new Optional>( + new InteractionMessageCallbackData( + Embeds: new Optional>( + [ + new Embed( + Colour: DiscordUtils.Red, + Title: "Something went wrong", + Description: $""" + Something went wrong while running this command. + > {cataloggerError.Message} + Please try again later. + """ + ), + ] + ) + ) + ) + ), + ct: ct + ); } } From a4a6fb5d31e74ad7c5ca4291877baeff1ebbdd55 Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 11 Apr 2025 14:41:09 +0200 Subject: [PATCH 17/18] refactor: clean up log channel resolution --- .../Services/WebhookExecutorService.cs | 220 +++++++++--------- 1 file changed, 114 insertions(+), 106 deletions(-) diff --git a/Catalogger.Backend/Services/WebhookExecutorService.cs b/Catalogger.Backend/Services/WebhookExecutorService.cs index 6b73cb4..8ca8cb3 100644 --- a/Catalogger.Backend/Services/WebhookExecutorService.cs +++ b/Catalogger.Backend/Services/WebhookExecutorService.cs @@ -43,7 +43,7 @@ public class WebhookExecutorService( private readonly ILogger _logger = logger.ForContext(); private readonly Snowflake _applicationId = DiscordSnowflake.New(config.Discord.ApplicationId); private readonly ConcurrentDictionary> _cache = new(); - private readonly ConcurrentDictionary _locks = new(); + private readonly ConcurrentDictionary _locks = new(); private readonly ConcurrentDictionary _timers = new(); private IUser? _selfUser; @@ -189,7 +189,7 @@ public class WebhookExecutorService( private List TakeFromQueue(ulong channelId) { var queue = _cache.GetOrAdd(channelId, []); - var channelLock = _locks.GetOrAdd(channelId, channelId); + var channelLock = _locks.GetOrAdd(channelId, new Lock()); lock (channelLock) { var totalContentLength = 0; @@ -293,10 +293,10 @@ public class WebhookExecutorService( roleIds != null && logChannelType is LogChannelType.GuildMemberUpdate; if (isMessageLog) - return GetMessageLogChannel(guild, logChannelType, channelId, userId); + return GetLogChannelForMessageEvent(guild, logChannelType, channelId, userId); if (isChannelLog) - return GetChannelLogChannel(guild, logChannelType, channelId!.Value); + return GetLogChannelForChannelEvent(guild, logChannelType, channelId!.Value); if (isRoleLog && guild.IgnoredRoles.Contains(roleId!.Value.Value)) return null; @@ -305,77 +305,11 @@ public class WebhookExecutorService( if (isMemberRoleUpdateLog && roleIds!.All(r => guild.IgnoredRoles.Contains(r.Value))) return null; - // If nothing is ignored, return the correct log channel! + // If nothing is ignored, and this isn't a message or channel event, return the default log channel. return GetDefaultLogChannel(guild, logChannelType); } - private ulong? GetChannelLogChannel( - Guild guild, - LogChannelType logChannelType, - Snowflake channelId - ) - { - _logger.Verbose( - "Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}", - logChannelType, - guild.Id, - channelId - ); - - if (!channelCache.TryGet(channelId, out var channel)) - { - _logger.Verbose( - "Channel with ID {ChannelId} is not cached, returning default log channel", - channelId - ); - return GetDefaultLogChannel(guild, logChannelType); - } - - Snowflake? categoryId; - if ( - channel.Type - is ChannelType.AnnouncementThread - or ChannelType.PrivateThread - or ChannelType.PublicThread - ) - { - // parent_id should always have a value for threads - channelId = channel.ParentID.Value!.Value; - if (!channelCache.TryGet(channelId, out var parentChannel)) - { - _logger.Verbose( - "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", - channelId - ); - return GetDefaultLogChannel(guild, logChannelType); - } - categoryId = parentChannel.ParentID.Value; - } - else - { - channelId = channel.ID; - categoryId = channel.ParentID.Value; - } - - // Check if the channel or its category is ignored - if ( - guild.IgnoredChannels.Contains(channelId.Value) - || (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value)) - ) - { - _logger.Verbose( - "Channel {ChannelId} or its parent {CategoryId} is ignored", - channelId, - categoryId - ); - return null; - } - - _logger.Verbose("Returning default log channel for {EventType}", logChannelType); - return GetDefaultLogChannel(guild, logChannelType); - } - - private ulong? GetMessageLogChannel( + private ulong? GetLogChannelForMessageEvent( Guild guild, LogChannelType logChannelType, Snowflake? channelId = null, @@ -415,41 +349,24 @@ public class WebhookExecutorService( return GetDefaultLogChannel(guild, logChannelType); } - Snowflake? categoryId; - if ( - channel.Type - is ChannelType.AnnouncementThread - or ChannelType.PrivateThread - or ChannelType.PublicThread - ) + if (!GetChannelAndParentId(channel, out var actualChannelId, out var categoryId)) { - // parent_id should always have a value for threads - channelId = channel.ParentID.Value!.Value; - if (!channelCache.TryGet(channelId.Value, out var parentChannel)) - { - _logger.Verbose( - "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", - channelId - ); - return GetDefaultLogChannel(guild, logChannelType); - } - categoryId = parentChannel.ParentID.Value; - } - else - { - channelId = channel.ID; - categoryId = channel.ParentID.Value; + _logger.Verbose( + "Could not get root channel and category ID for channel {ChannelId}, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); } // Check if the channel or its category is ignored if ( - guild.Messages.IgnoredChannels.Contains(channelId.Value.Value) + guild.Messages.IgnoredChannels.Contains(actualChannelId.Value) || categoryId != null && guild.Messages.IgnoredChannels.Contains(categoryId.Value.Value) ) { _logger.Verbose( "Channel {ChannelId} or its parent {CategoryId} is ignored", - channelId, + actualChannelId, categoryId ); return null; @@ -459,8 +376,10 @@ public class WebhookExecutorService( { // Check the channel-local and category-local ignored users var channelIgnoredUsers = - guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(channelId.Value.Value) + guild.Messages.IgnoredUsersPerChannel.GetValueOrDefault(actualChannelId.Value) ?? []; + + // Obviously, we can only check for category-level ignored users if we actually got a category ID. var categoryIgnoredUsers = ( categoryId != null @@ -469,6 +388,8 @@ public class WebhookExecutorService( ) : [] ) ?? []; + + // Combine the ignored users in the channel and category, then check if the user is in there. if (channelIgnoredUsers.Concat(categoryIgnoredUsers).Contains(userId.Value)) { _logger.Verbose( @@ -482,7 +403,7 @@ public class WebhookExecutorService( } // These three events can be redirected to other channels. Redirects can be on a channel or category level. - // The events are only redirected if they're supposed to be logged in the first place. + // The events are only redirected if they're supposed to be logged in the first place (i.e. GetDefaultLogChannel doesn't return 0) if (GetDefaultLogChannel(guild, logChannelType) == 0) { _logger.Verbose( @@ -492,21 +413,21 @@ public class WebhookExecutorService( return null; } - var categoryRedirect = - categoryId != null - ? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value) - : 0; - - if (guild.Channels.Redirects.TryGetValue(channelId.Value.Value, out var channelRedirect)) + if (guild.Channels.Redirects.TryGetValue(actualChannelId.Value, out var channelRedirect)) { _logger.Verbose( "Messages from channel {ChannelId} should be redirected to {RedirectId}", - channelId, + actualChannelId, channelRedirect ); return channelRedirect; } + var categoryRedirect = + categoryId != null + ? guild.Channels.Redirects.GetValueOrDefault(categoryId.Value.Value) + : 0; + if (categoryRedirect != 0) { _logger.Verbose( @@ -514,6 +435,7 @@ public class WebhookExecutorService( categoryId, categoryRedirect ); + return categoryRedirect; } _logger.Verbose( @@ -523,6 +445,92 @@ public class WebhookExecutorService( return GetDefaultLogChannel(guild, logChannelType); } + private ulong? GetLogChannelForChannelEvent( + Guild guild, + LogChannelType logChannelType, + Snowflake channelId + ) + { + _logger.Verbose( + "Getting log channel for event {Event} in guild {GuildId} and channel {ChannelId}", + logChannelType, + guild.Id, + channelId + ); + + if (!channelCache.TryGet(channelId, out var channel)) + { + _logger.Verbose( + "Channel with ID {ChannelId} is not cached, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); + } + + if (!GetChannelAndParentId(channel, out channelId, out var categoryId)) + { + _logger.Verbose( + "Could not get root channel and category ID for channel {ChannelId}, returning default log channel", + channelId + ); + return GetDefaultLogChannel(guild, logChannelType); + } + + // Check if the channel or its category is ignored + if ( + guild.IgnoredChannels.Contains(channelId.Value) + || (categoryId != null && guild.IgnoredChannels.Contains(categoryId.Value.Value)) + ) + { + _logger.Verbose( + "Channel {ChannelId} or its parent {CategoryId} is ignored", + channelId, + categoryId + ); + return null; + } + + _logger.Verbose("Returning default log channel for {EventType}", logChannelType); + return GetDefaultLogChannel(guild, logChannelType); + } + + private bool GetChannelAndParentId( + IChannel channel, + out Snowflake channelId, + out Snowflake? categoryId + ) + { + if ( + channel.Type + is ChannelType.AnnouncementThread + or ChannelType.PrivateThread + or ChannelType.PublicThread + ) + { + // parent_id should always have a value for threads + channelId = channel.ParentID.Value!.Value; + if (!channelCache.TryGet(channelId, out var parentChannel)) + { + _logger.Verbose( + "Parent channel for thread {ChannelId} is not in cache, returning the default log channel", + channelId + ); + + channelId = Snowflake.CreateTimestampSnowflake(); + categoryId = null; + return false; + } + categoryId = parentChannel.ParentID.Value; + } + else + { + channelId = channel.ID; + categoryId = channel.ParentID.Value; + } + + return true; + } + public static ulong GetDefaultLogChannel(Guild guild, LogChannelType logChannelType) => logChannelType switch { From 9f3dfc74d699e946604c2d2d17d57f2b4c30c76c Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 11 Apr 2025 14:58:21 +0200 Subject: [PATCH 18/18] fix: make prometheus base url configurable --- Catalogger.Backend/Bot/Commands/MetaCommands.cs | 5 +++-- Catalogger.Backend/Config.cs | 1 + Catalogger.Backend/config.example.ini | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Catalogger.Backend/Bot/Commands/MetaCommands.cs b/Catalogger.Backend/Bot/Commands/MetaCommands.cs index 1887507..8b543af 100644 --- a/Catalogger.Backend/Bot/Commands/MetaCommands.cs +++ b/Catalogger.Backend/Bot/Commands/MetaCommands.cs @@ -218,7 +218,7 @@ public class MetaCommands( await channelApi.EditMessageAsync(msg.ChannelID, msg.ID, content: "", embeds: embeds); } - // TODO: add more checks around response format, configurable prometheus endpoint + // TODO: add more checks around response format private async Task MessagesRate() { if (!config.Logging.EnableMetrics) @@ -227,7 +227,8 @@ public class MetaCommands( try { var query = HttpUtility.UrlEncode("increase(catalogger_received_messages[5m])"); - var resp = await _client.GetAsync($"http://localhost:9090/api/v1/query?query={query}"); + var prometheusUrl = config.Logging.PrometheusUrl ?? "http://localhost:9090"; + var resp = await _client.GetAsync($"{prometheusUrl}/api/v1/query?query={query}"); resp.EnsureSuccessStatusCode(); var data = await resp.Content.ReadFromJsonAsync(); diff --git a/Catalogger.Backend/Config.cs b/Catalogger.Backend/Config.cs index 612b91e..831c439 100644 --- a/Catalogger.Backend/Config.cs +++ b/Catalogger.Backend/Config.cs @@ -33,6 +33,7 @@ public class Config public bool EnableMetrics { get; init; } = true; public string? SeqLogUrl { get; init; } + public string? PrometheusUrl { get; init; } } public class DatabaseConfig diff --git a/Catalogger.Backend/config.example.ini b/Catalogger.Backend/config.example.ini index 9b74c55..3f12c79 100644 --- a/Catalogger.Backend/config.example.ini +++ b/Catalogger.Backend/config.example.ini @@ -7,6 +7,9 @@ LogQueries = false SeqLogUrl = http://localhost:5341 # Whether to enable Prometheus metrics. If disabled, Catalogger will update metrics manually every so often. EnableMetrics = false +# The URL for the Prometheus server. Used for message rate if metrics are enabled. +# Defaults to http://localhost:9090, should be changed if Prometheus is on another server. +PrometheusUrl = http://localhost:9090 [Database] Url = Host=localhost;Database=postgres;Username=postgres;Password=postgres