diff --git a/Catalogger.Backend/Config.cs b/Catalogger.Backend/Config.cs index 2a60d6a..612b91e 100644 --- a/Catalogger.Backend/Config.cs +++ b/Catalogger.Backend/Config.cs @@ -60,6 +60,9 @@ public class Config // If enabled, nothing will be logged. public bool TestMode { get; init; } = false; + + // Token for discord.bots.gg stats + public string? BotsGgToken { get; init; } } public class WebConfig diff --git a/Catalogger.Backend/Services/StatusUpdateService.cs b/Catalogger.Backend/Services/StatusUpdateService.cs index 6c0dff7..1d858aa 100644 --- a/Catalogger.Backend/Services/StatusUpdateService.cs +++ b/Catalogger.Backend/Services/StatusUpdateService.cs @@ -13,6 +13,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +using System.Net.Http.Headers; +using System.Text.Json; +using System.Text.Json.Serialization; using Catalogger.Backend.Bot; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Gateway.Commands; @@ -24,15 +27,16 @@ public class StatusUpdateService(ILogger logger, ShardedGatewayClient shardedCli : BackgroundService { private readonly ILogger _logger = logger.ForContext(); + private readonly HttpClient _client = new(); protected override async Task ExecuteAsync(CancellationToken ct) { using var timer = new PeriodicTimer(TimeSpan.FromMinutes(3)); while (await timer.WaitForNextTickAsync(ct)) - UpdateShardStatuses(ct); + await UpdateShardStatuses(ct); } - private void UpdateShardStatuses(CancellationToken ct = default) + private async Task UpdateShardStatuses(CancellationToken ct = default) { _logger.Information( "Updating status for {TotalShards} shards. Guild count is {GuildCount}", @@ -59,6 +63,8 @@ public class StatusUpdateService(ILogger logger, ShardedGatewayClient shardedCli client.SubmitCommand(PresenceFor(shardId)); } + + await ReportStatsAsync(); } private UpdatePresence PresenceFor(int shardId) @@ -75,4 +81,43 @@ public class StatusUpdateService(ILogger logger, ShardedGatewayClient shardedCli Activities: [new Activity(Name: "Beep", Type: ActivityType.Custom, State: status)] ); } + + private async Task ReportStatsAsync() + { + if (config.Discord.BotsGgToken == null) + return; + + _logger.Debug("Posting stats to discord.bots.gg"); + + var req = new HttpRequestMessage( + HttpMethod.Post, + $"https://discord.bots.gg/api/v1/bots/{config.Discord.ApplicationId}/stats" + ); + req.Headers.Add("Authorization", config.Discord.BotsGgToken); + req.Content = new StringContent( + JsonSerializer.Serialize( + new BotsGgStats( + (int)CataloggerMetrics.GuildsCached.Value, + shardedClient.TotalShards + ) + ), + new MediaTypeHeaderValue("application/json", "utf-8") + ); + + var resp = await _client.SendAsync(req); + if (!resp.IsSuccessStatusCode) + { + var content = await resp.Content.ReadAsStringAsync(); + _logger.Error( + "Error updating stats for discord.bots.gg: {StatusCode}, {Content}", + (int)resp.StatusCode, + content + ); + } + } + + private record BotsGgStats( + [property: JsonPropertyName("guildCount")] int GuildCount, + [property: JsonPropertyName("shardCount")] int ShardCount + ); }