using Catalogger.Backend.Cache; using Catalogger.Backend.Cache.InMemoryCache; using Catalogger.Backend.Database; using Catalogger.Backend.Database.Queries; using Catalogger.Backend.Extensions; using Catalogger.Backend.Services; using Humanizer; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Gateway.Events; using Remora.Discord.API.Abstractions.Rest; using Remora.Discord.API.Objects; using Remora.Discord.Extensions.Embeds; using Remora.Discord.Gateway.Responders; using Remora.Results; namespace Catalogger.Backend.Bot.Responders.Guilds; public class GuildMemberAddResponder( ILogger logger, DatabaseContext db, IMemberCache memberCache, UserCache userCache, WebhookExecutorService webhookExecutor, IDiscordRestGuildAPI guildApi, PluralkitApiService pluralkitApi) : IResponder { private readonly ILogger _logger = logger.ForContext(); private static readonly TimeSpan NewAccountThreshold = 7.Days(); public async Task RespondAsync(IGuildMemberAdd member, CancellationToken ct = default) { await memberCache.SetAsync(member.GuildID, member); var user = member.User.GetOrThrow(); var builder = new EmbedBuilder() .WithTitle("Member joined") .WithColour(DiscordUtils.Green) .WithAuthor(user.Tag(), null, user.AvatarUrl()) .WithDescription($"<@{user.ID}>") .WithCurrentTimestamp() .WithFooter($"ID: {user.ID}"); var guildConfig = await db.GetGuildAsync(member.GuildID, ct); var guildRes = await guildApi.GetGuildAsync(member.GuildID, withCounts: true, ct); if (guildRes.IsSuccess) builder.Description += $"\n{guildRes.Entity.ApproximateMemberCount.Value.Ordinalize()} to join"; builder.Description += $"\ncreated {user.ID.Timestamp.Prettify()} ago\n"; var pkSystem = await pluralkitApi.GetPluralKitSystemAsync(user.ID.Value, ct); if (pkSystem != null) { var createdAt = pkSystem.Created != null ? $"{pkSystem.Created.Value.Prettify()} ago ()" : "*(unknown)*"; builder.AddField("PluralKit system", $""" **ID:** {pkSystem.Id} (`{pkSystem.Uuid}`) **Name:** {pkSystem.Name ?? "*(none)*"} **Tag:** {pkSystem.Tag ?? "*(none)*"} **Created:** {createdAt} """); } // TODO: find used invite List embeds = [builder.Build().GetOrThrow()]; if (user.ID.Timestamp > DateTimeOffset.Now - NewAccountThreshold) { embeds.Add(new EmbedBuilder() .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()); } if (pkSystem != null) { if (guildConfig.BannedSystems.Contains(pkSystem.Id) || guildConfig.BannedSystems.Contains(pkSystem.Uuid.ToString())) { embeds.Add(new EmbedBuilder().WithTitle("Banned system") .WithDescription( "\u26a0\ufe0f The system associated with this account has been banned from the server.") .WithColour(DiscordUtils.Red) .WithFooter($"ID: {pkSystem.Id}") .Build() .GetOrThrow()); } } if (embeds.Count > 1) await webhookExecutor.SendLogWithAttachmentsAsync(guildConfig.Channels.GuildMemberAdd, embeds, []); else await webhookExecutor.QueueLogAsync(guildConfig.Channels.GuildMemberAdd, embeds[0]); return Result.Success; } }