feat: member add logs, improve meta command

This commit is contained in:
sam 2024-08-21 17:31:39 +02:00
parent f0cb5a9d03
commit 8f39d85486
Signed by: sam
GPG key ID: 5F3C3C1B3166639D
12 changed files with 73 additions and 15 deletions

View file

@ -6,6 +6,7 @@ using System.Web;
using Catalogger.Backend.Cache.InMemoryCache; using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Extensions; using Catalogger.Backend.Extensions;
using Humanizer; using Humanizer;
using Humanizer.Localisation;
using Remora.Commands.Attributes; using Remora.Commands.Attributes;
using Remora.Commands.Groups; using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Objects;
@ -60,8 +61,14 @@ public class MetaCommands(
true); true);
embed.AddField("Numbers", embed.AddField("Numbers",
$"{CataloggerMetrics.MessagesStored.Value:N0} messages from {guildCache.Size:N0} servers\nCached {channelCache.Size:N0} channels", $"{CataloggerMetrics.MessagesStored.Value:N0} messages " +
inline: false); $"from {guildCache.Size:N0} servers\nCached {channelCache.Size:N0} channels",
true);
embed.AddField("Uptime",
$"{(CataloggerMetrics.Startup - clock.GetCurrentInstant()).Prettify(TimeUnit.Second)}\n" +
$"since <t:{CataloggerMetrics.Startup.ToUnixTimeSeconds()}:F>",
true);
IEmbed[] embeds = [embed.Build().GetOrThrow()]; IEmbed[] embeds = [embed.Build().GetOrThrow()];
@ -80,9 +87,7 @@ public class MetaCommands(
resp.EnsureSuccessStatusCode(); resp.EnsureSuccessStatusCode();
var data = await resp.Content.ReadFromJsonAsync<PrometheusResponse>(); var data = await resp.Content.ReadFromJsonAsync<PrometheusResponse>();
_logger.Debug("Raw json: {Data}", JsonSerializer.Serialize(data));
var rawNumber = (data?.data.result[0].value[1] as JsonElement?)?.GetString(); var rawNumber = (data?.data.result[0].value[1] as JsonElement?)?.GetString();
_logger.Debug("Raw data: {Raw}", rawNumber);
return double.TryParse(rawNumber, out var rate) ? rate : null; return double.TryParse(rawNumber, out var rate) ? rate : null;
} }
catch (Exception e) catch (Exception e)

View file

@ -12,4 +12,5 @@ public static class DiscordUtils
public static readonly Color Purple = Color.FromArgb(155, 89, 182); public static readonly Color Purple = Color.FromArgb(155, 89, 182);
public static readonly Color Green = Color.FromArgb(46, 204, 113); public static readonly Color Green = Color.FromArgb(46, 204, 113);
public static readonly Color Blue = Color.FromArgb(52, 152, 219); public static readonly Color Blue = Color.FromArgb(52, 152, 219);
public static readonly Color Orange = Color.FromArgb(230, 126, 34);
} }

View file

@ -1,9 +1,11 @@
using Catalogger.Backend.Cache; using Catalogger.Backend.Cache;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database; using Catalogger.Backend.Database;
using Catalogger.Backend.Database.Queries; using Catalogger.Backend.Database.Queries;
using Catalogger.Backend.Extensions; using Catalogger.Backend.Extensions;
using Catalogger.Backend.Services; using Catalogger.Backend.Services;
using Humanizer; using Humanizer;
using Remora.Discord.API;
using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.API.Objects; using Remora.Discord.API.Objects;
@ -17,6 +19,7 @@ public class GuildMemberAddResponder(
ILogger logger, ILogger logger,
DatabaseContext db, DatabaseContext db,
IMemberCache memberCache, IMemberCache memberCache,
UserCache userCache,
WebhookExecutorService webhookExecutor, WebhookExecutorService webhookExecutor,
IDiscordRestGuildAPI guildApi, IDiscordRestGuildAPI guildApi,
PluralkitApiService pluralkitApi) : IResponder<IGuildMemberAdd> PluralkitApiService pluralkitApi) : IResponder<IGuildMemberAdd>
@ -43,13 +46,14 @@ public class GuildMemberAddResponder(
if (guildRes.IsSuccess) if (guildRes.IsSuccess)
builder.Description += $"\n{guildRes.Entity.ApproximateMemberCount.Value.Ordinalize()} to join"; builder.Description += $"\n{guildRes.Entity.ApproximateMemberCount.Value.Ordinalize()} to join";
builder.Description += $"\ncreated <t:{user.ID.Timestamp.ToUnixTimeSeconds()}>"; builder.Description +=
$"\ncreated {user.ID.Timestamp.Prettify()} ago\n<t:{user.ID.Timestamp.ToUnixTimeSeconds()}:F>";
var pkSystem = await pluralkitApi.GetPluralKitSystemAsync(user.ID.Value, ct); var pkSystem = await pluralkitApi.GetPluralKitSystemAsync(user.ID.Value, ct);
if (pkSystem != null) if (pkSystem != null)
{ {
var createdAt = pkSystem.Created != null var createdAt = pkSystem.Created != null
? $"<t:{pkSystem.Created.Value.ToUnixTimeSeconds()}>" ? $"{pkSystem.Created.Value.Prettify()} ago (<t:{pkSystem.Created.Value.ToUnixTimeSeconds()}:F>)"
: "*(unknown)*"; : "*(unknown)*";
builder.AddField("PluralKit system", $""" builder.AddField("PluralKit system", $"""
**ID:** {pkSystem.Id} (`{pkSystem.Uuid}`) **ID:** {pkSystem.Id} (`{pkSystem.Uuid}`)
@ -65,8 +69,28 @@ public class GuildMemberAddResponder(
if (user.ID.Timestamp > DateTimeOffset.Now - NewAccountThreshold) if (user.ID.Timestamp > DateTimeOffset.Now - NewAccountThreshold)
{ {
embeds.Add(new EmbedBuilder().WithTitle("New account") embeds.Add(new EmbedBuilder()
.WithDescription($"\u26a0\ufe0f Created <t:{user.ID.Timestamp.ToUnixTimeSeconds()}:R>").Build() .WithTitle("New account")
.WithColour(DiscordUtils.Orange)
.WithDescription($"\u26a0\ufe0f Created {user.ID.Timestamp.Prettify()} ago")
.Build()
.GetOrThrow());
}
var watchlist = await db.GetWatchlistEntryAsync(member.GuildID, user.ID, ct);
if (watchlist != null)
{
var moderator = await userCache.GetUserAsync(DiscordSnowflake.New(watchlist.ModeratorId));
var mod = moderator != null ? $"{moderator.Tag()} (<@{moderator.ID}>)" : $"<@{watchlist.ModeratorId}>";
embeds.Add(new EmbedBuilder()
.WithTitle("⚠️ User on watchlist")
.WithColour(DiscordUtils.Red)
.WithDescription($"**{user.Tag()}** is on this server's watch list.\n\n{watchlist.Reason}")
.WithFooter($"ID: {user.ID} | Added")
.WithTimestamp(watchlist.AddedAt.ToDateTimeOffset())
.AddField("Moderator", mod).GetOrThrow()
.Build()
.GetOrThrow()); .GetOrThrow());
} }

View file

@ -1,9 +1,12 @@
using NodaTime;
using Prometheus; using Prometheus;
namespace Catalogger.Backend; namespace Catalogger.Backend;
public static class CataloggerMetrics public static class CataloggerMetrics
{ {
public static Instant Startup { get; set; }
public static readonly Gauge MessagesReceived = public static readonly Gauge MessagesReceived =
Metrics.CreateGauge("catalogger_received_messages", "Number of messages Catalogger has received"); Metrics.CreateGauge("catalogger_received_messages", "Number of messages Catalogger has received");

View file

@ -13,7 +13,7 @@ public class Config
{ {
public LogEventLevel LogEventLevel { get; init; } = LogEventLevel.Debug; public LogEventLevel LogEventLevel { get; init; } = LogEventLevel.Debug;
public bool LogQueries { get; init; } = false; public bool LogQueries { get; init; } = false;
public int MetricsPort { get; init; } = 5001; public int MetricsPort { get; init; } = 5001;
public bool EnableMetrics { get; init; } = true; public bool EnableMetrics { get; init; } = true;
} }

View file

@ -15,8 +15,12 @@ public static class QueryExtensions
public static async ValueTask<Guild> GetGuildAsync(this DatabaseContext db, ulong id, public static async ValueTask<Guild> GetGuildAsync(this DatabaseContext db, ulong id,
CancellationToken ct = default) CancellationToken ct = default)
{ {
var guild = await db.Guilds.FindAsync(id); var guild = await db.Guilds.FindAsync([id], ct);
if (guild == null) throw new CataloggerError("Guild not found, was not initialized during guild create"); if (guild == null) throw new CataloggerError("Guild not found, was not initialized during guild create");
return guild; return guild;
} }
public static async Task<Watchlist?> GetWatchlistEntryAsync(this DatabaseContext db, Snowflake guildId,
Snowflake userId, CancellationToken ct = default) =>
await db.Watchlists.FindAsync([guildId.Value, userId.Value], ct);
} }

View file

@ -1 +0,0 @@
namespace Catalogger.Backend.Database;

View file

@ -103,6 +103,8 @@ public static class StartupExtensions
var logger = scope.ServiceProvider.GetRequiredService<ILogger>().ForContext<Program>(); var logger = scope.ServiceProvider.GetRequiredService<ILogger>().ForContext<Program>();
logger.Information("Starting Catalogger.NET"); logger.Information("Starting Catalogger.NET");
CataloggerMetrics.Startup = scope.ServiceProvider.GetRequiredService<IClock>().GetCurrentInstant();
await using (var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>()) await using (var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>())
{ {
var migrationCount = (await db.Database.GetPendingMigrationsAsync()).Count(); var migrationCount = (await db.Database.GetPendingMigrationsAsync()).Count();

View file

@ -0,0 +1,20 @@
using Humanizer;
using Humanizer.Localisation;
using NodaTime;
namespace Catalogger.Backend.Extensions;
public static class TimeExtensions
{
public static string Prettify(this TimeSpan timespan, TimeUnit minUnit = TimeUnit.Minute) =>
timespan.Humanize(minUnit: minUnit, precision: 5, collectionSeparator: null);
public static string Prettify(this Duration duration, TimeUnit minUnit = TimeUnit.Minute) =>
duration.ToTimeSpan().Prettify(minUnit);
public static string Prettify(this DateTimeOffset datetime, TimeUnit minUnit = TimeUnit.Minute) =>
(datetime - DateTimeOffset.Now).Prettify(minUnit);
public static string Prettify(this Instant instant, TimeUnit minUnit = TimeUnit.Minute) =>
(instant - SystemClock.Instance.GetCurrentInstant()).Prettify(minUnit);
}

View file

@ -45,11 +45,11 @@ public class MetricsCollectionService(
public class BackgroundMetricsCollectionService(ILogger logger, MetricsCollectionService innerService) : BackgroundService public class BackgroundMetricsCollectionService(ILogger logger, MetricsCollectionService innerService) : BackgroundService
{ {
private readonly ILogger _logger = logger.ForContext<BackgroundMetricsCollectionService>(); private readonly ILogger _logger = logger.ForContext<BackgroundMetricsCollectionService>();
protected override async Task ExecuteAsync(CancellationToken ct) protected override async Task ExecuteAsync(CancellationToken ct)
{ {
_logger.Information("Metrics are disabled, periodically collecting metrics manually"); _logger.Information("Metrics are disabled, periodically collecting metrics manually");
using var timer = new PeriodicTimer(1.Minutes()); using var timer = new PeriodicTimer(1.Minutes());
while (await timer.WaitForNextTickAsync(ct)) while (await timer.WaitForNextTickAsync(ct))
{ {

View file

@ -47,7 +47,7 @@ public class PluralkitApiService(ILogger logger)
} }
return await resp.Content.ReadFromJsonAsync<T>(new JsonSerializerOptions return await resp.Content.ReadFromJsonAsync<T>(new JsonSerializerOptions
{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }, ct) ?? { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }, ct) ??
throw new CataloggerError("JSON response from PluralKit API was null"); throw new CataloggerError("JSON response from PluralKit API was null");
} }

View file

@ -56,7 +56,7 @@ public class WebhookExecutorService(
public async Task QueueLogAsync(ulong channelId, IEmbed embed) public async Task QueueLogAsync(ulong channelId, IEmbed embed)
{ {
if (channelId == 0) return; if (channelId == 0) return;
var queue = _cache.GetOrAdd(channelId, []); var queue = _cache.GetOrAdd(channelId, []);
queue.Enqueue(embed); queue.Enqueue(embed);
_cache[channelId] = queue; _cache[channelId] = queue;