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.Objects; 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.SendLogAsync( guildConfig.Channels.GuildMemberAdd, embeds.Cast().ToList(), [] ); else webhookExecutor.QueueLog(guildConfig.Channels.GuildMemberAdd, embeds[0]); return Result.Success; } }