feat: member add logs, improve meta command
This commit is contained in:
parent
f0cb5a9d03
commit
8f39d85486
12 changed files with 73 additions and 15 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
namespace Catalogger.Backend.Database;
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
20
Catalogger.Backend/Extensions/TimeExtensions.cs
Normal file
20
Catalogger.Backend/Extensions/TimeExtensions.cs
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue