feat: role delete logging, used invite logging, also some random changes
This commit is contained in:
parent
4f54077c68
commit
c906a4d6b6
18 changed files with 386 additions and 76 deletions
|
|
@ -5,6 +5,7 @@ using Catalogger.Backend.Database.Queries;
|
|||
using Catalogger.Backend.Extensions;
|
||||
using Catalogger.Backend.Services;
|
||||
using Humanizer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Remora.Discord.API;
|
||||
using Remora.Discord.API.Abstractions.Gateway.Events;
|
||||
using Remora.Discord.API.Abstractions.Objects;
|
||||
|
|
@ -20,6 +21,7 @@ public class GuildMemberAddResponder(
|
|||
ILogger logger,
|
||||
DatabaseContext db,
|
||||
IMemberCache memberCache,
|
||||
IInviteCache inviteCache,
|
||||
UserCache userCache,
|
||||
WebhookExecutorService webhookExecutor,
|
||||
IDiscordRestGuildAPI guildApi,
|
||||
|
|
@ -70,7 +72,62 @@ public class GuildMemberAddResponder(
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: find used invite
|
||||
// Bots don't use invites, so the entire following block is useless
|
||||
if (user.IsBot.OrDefault())
|
||||
{
|
||||
goto afterInvite;
|
||||
}
|
||||
|
||||
var existingInvites = (await inviteCache.TryGetAsync(member.GuildID)).ToList();
|
||||
var newInvitesRes = await guildApi.GetGuildInvitesAsync(member.GuildID, ct);
|
||||
if (!newInvitesRes.IsSuccess)
|
||||
{
|
||||
_logger.Error(
|
||||
"Could not fetch invites for guild {GuildId}: {Error}",
|
||||
member.GuildID,
|
||||
newInvitesRes.Error
|
||||
);
|
||||
|
||||
goto afterInvite;
|
||||
}
|
||||
|
||||
// Update the invite cache immediately--we've already fetched a copy of the invites, after all
|
||||
await inviteCache.SetAsync(member.GuildID, newInvitesRes.Entity);
|
||||
|
||||
// If we can't find a used invite, and the guild has a vanity link, that invite was used.
|
||||
// Otherwise, we give up
|
||||
var usedInvite = FindUsedInvite(existingInvites, newInvitesRes.Entity);
|
||||
if (usedInvite == null)
|
||||
{
|
||||
builder.AddField(
|
||||
"Invite used",
|
||||
guildRes is { IsSuccess: true, Entity.VanityUrlCode: not null }
|
||||
? $"Vanity invite (`{guildRes.Entity.VanityUrlCode}`)"
|
||||
: "*Could not determine invite, sorry.*"
|
||||
);
|
||||
|
||||
goto afterInvite;
|
||||
}
|
||||
|
||||
var inviteName =
|
||||
await db
|
||||
.Invites.Where(i => i.Code == usedInvite.Code && i.GuildId == member.GuildID.Value)
|
||||
.Select(i => i.Name)
|
||||
.FirstOrDefaultAsync(ct) ?? "*(unnamed)*";
|
||||
|
||||
var inviteDescription = $"""
|
||||
**Code:** {usedInvite.Code}
|
||||
**Name:** {inviteName}
|
||||
**Uses:** {usedInvite.Uses}
|
||||
**Created at:** <t:{usedInvite.CreatedAt.ToUnixTimeSeconds()}>
|
||||
""";
|
||||
|
||||
if (usedInvite.Inviter.IsDefined(out var inviter))
|
||||
inviteDescription += $"\n**Created by:** {inviter.Tag()} <@{inviter.ID}>";
|
||||
|
||||
builder.AddField("Invite used", inviteDescription);
|
||||
|
||||
afterInvite:
|
||||
|
||||
List<Embed> embeds = [builder.Build().GetOrThrow()];
|
||||
|
||||
|
|
@ -145,4 +202,32 @@ public class GuildMemberAddResponder(
|
|||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private static IInviteWithMetadata? FindUsedInvite(
|
||||
List<IInviteWithMetadata> existingInvites,
|
||||
IReadOnlyList<IInviteWithMetadata> newInvites
|
||||
)
|
||||
{
|
||||
// First, we check all invites in *both* lists, and look for one that has more uses now than before.
|
||||
// If one matches that, it's probably the used invite.
|
||||
var usedInvite = existingInvites.FirstOrDefault(e =>
|
||||
newInvites.Any(n => e.Code == n.Code && e.Uses < n.Uses)
|
||||
);
|
||||
if (usedInvite != null)
|
||||
return usedInvite;
|
||||
|
||||
// Then we check all new invites (i.e. ones that don't exist in the old list, but do in the new one)
|
||||
// and check for one that has one use.
|
||||
usedInvite = newInvites.FirstOrDefault(n =>
|
||||
existingInvites.All(e => e.Code != n.Code) && n.Uses == 1
|
||||
);
|
||||
if (usedInvite != null)
|
||||
return usedInvite;
|
||||
|
||||
// Finally, we check invites that exist in the old list but not the new one, and were one use away from expiry.
|
||||
// If one matches, we can safely say it was the used invite.
|
||||
return existingInvites.FirstOrDefault(e =>
|
||||
newInvites.All(n => n.Code != e.Code) && e.MaxUses == e.Uses - 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue