private static void AppendPiracyStats(DiscordEmbedBuilder embed) { try { using var db = new BotDb(); var timestamps = db.Warning .Where(w => w.Timestamp.HasValue && !w.Retracted) .OrderBy(w => w.Timestamp) .Select(w => w.Timestamp !.Value) .ToList(); var firstWarnTimestamp = timestamps.FirstOrDefault(); var previousTimestamp = firstWarnTimestamp; var longestGapBetweenWarning = 0L; long longestGapStart = 0L, longestGapEnd = 0L; var span24H = TimeSpan.FromHours(24).Ticks; var currentSpan = new Queue <long>(); long mostWarningsEnd = 0L, daysWithoutWarnings = 0L; var mostWarnings = 0; for (var i = 1; i < timestamps.Count; i++) { var currentTimestamp = timestamps[i]; var newGap = currentTimestamp - previousTimestamp; if (newGap > longestGapBetweenWarning) { longestGapBetweenWarning = newGap; longestGapStart = previousTimestamp; longestGapEnd = currentTimestamp; } if (newGap > span24H) { daysWithoutWarnings += newGap / span24H; } currentSpan.Enqueue(currentTimestamp); while (currentSpan.Count > 0 && currentTimestamp - currentSpan.Peek() > span24H) { currentSpan.Dequeue(); } if (currentSpan.Count > mostWarnings) { mostWarnings = currentSpan.Count; currentSpan.Peek(); mostWarningsEnd = currentTimestamp; } previousTimestamp = currentTimestamp; } var utcNow = DateTime.UtcNow; var yesterday = utcNow.AddDays(-1).Ticks; var last24HWarnings = db.Warning.Where(w => w.Timestamp > yesterday && !w.Retracted).ToList(); var warnCount = last24HWarnings.Count; if (warnCount > mostWarnings) { mostWarnings = warnCount; mostWarningsEnd = utcNow.Ticks; } var lastWarn = timestamps.Any() ? timestamps.Last() : (long?)null; if (lastWarn.HasValue) { var currentGapBetweenWarnings = utcNow.Ticks - lastWarn.Value; if (currentGapBetweenWarnings > longestGapBetweenWarning) { longestGapBetweenWarning = currentGapBetweenWarnings; longestGapStart = lastWarn.Value; longestGapEnd = utcNow.Ticks; } daysWithoutWarnings += currentGapBetweenWarnings / span24H; } // most warnings per 24h var statsBuilder = new StringBuilder(); var rightDate = longestGapEnd == utcNow.Ticks ? "now" : longestGapEnd.AsUtc().ToString("yyyy-MM-dd"); if (longestGapBetweenWarning > 0) { statsBuilder.AppendLine($"Longest between warnings: **{TimeSpan.FromTicks(longestGapBetweenWarning).AsShortTimespan()}** between {longestGapStart.AsUtc():yyyy-MM-dd} and {rightDate}"); } rightDate = mostWarningsEnd == utcNow.Ticks ? "today" : $"on {mostWarningsEnd.AsUtc():yyyy-MM-dd}"; if (mostWarnings > 0) { statsBuilder.AppendLine($"Most warnings in 24h: **{mostWarnings}** {rightDate}"); } if (daysWithoutWarnings > 0 && firstWarnTimestamp > 0) { statsBuilder.AppendLine($"Full days without warnings: **{daysWithoutWarnings}** out of {(DateTime.UtcNow - firstWarnTimestamp.AsUtc()).TotalDays:0}"); } { statsBuilder.Append($"Warnings in the last 24h: **{warnCount}**"); if (warnCount == 0) { statsBuilder.Append(' ').Append(BotReactionsHandler.RandomPositiveReaction); } statsBuilder.AppendLine(); } if (lastWarn.HasValue) { statsBuilder.AppendLine($"Time since last warning: {(DateTime.UtcNow - lastWarn.Value.AsUtc()).AsShortTimespan()}"); } embed.AddField("Warning Stats", statsBuilder.ToString().TrimEnd(), true); } catch (Exception e) { Config.Log.Warn(e); } }
public async Task <DiscordEmbed> CreateMemberEmbed(PKSystem system, PKMember member, DiscordGuild guild, LookupContext ctx) { // string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone)); var name = member.NameFor(ctx); if (system.Name != null) { name = $"{name} ({system.Name})"; } DiscordColor color; try { color = member.Color?.ToDiscordColor() ?? DiscordUtils.Gray; } catch (ArgumentException) { // Bad API use can cause an invalid color string // TODO: fix that in the API // for now we just default to a blank color, yolo color = DiscordUtils.Gray; } await using var conn = await _db.Obtain(); var guildSettings = guild != null ? await _repo.GetMemberGuild(conn, guild.Id, member.Id) : null; var guildDisplayName = guildSettings?.DisplayName; var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx); var groups = await _repo.GetMemberGroups(conn, member.Id) .Where(g => g.Visibility.CanAccess(ctx)) .OrderBy(g => g.Name, StringComparer.InvariantCultureIgnoreCase) .ToListAsync(); var eb = new DiscordEmbedBuilder() // TODO: add URL of website when that's up .WithAuthor(name, iconUrl: DiscordUtils.WorkaroundForUrlBug(avatar)) // .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray) .WithColor(color) .WithFooter($"System ID: {system.Hid} | Member ID: {member.Hid} {(member.MetadataPrivacy.CanAccess(ctx) ? $"| Created on {member.Created.FormatZoned(system)}":"")}"); var description = ""; if (member.MemberVisibility == PrivacyLevel.Private) { description += "*(this member is hidden)*\n"; } if (guildSettings?.AvatarUrl != null) { if (member.AvatarFor(ctx) != null) { description += $"*(this member has a server-specific avatar set; [click here]({member.AvatarUrl}) to see the global avatar)*\n"; } else { description += "*(this member has a server-specific avatar set)*\n"; } } if (description != "") { eb.WithDescription(description); } if (avatar != null) { eb.WithThumbnail(avatar); } if (!member.DisplayName.EmptyOrNull() && member.NamePrivacy.CanAccess(ctx)) { eb.AddField("Display Name", member.DisplayName.Truncate(1024), true); } if (guild != null && guildDisplayName != null) { eb.AddField($"Server Nickname (for {guild.Name})", guildDisplayName.Truncate(1024), true); } if (member.BirthdayFor(ctx) != null) { eb.AddField("Birthdate", member.BirthdayString, true); } if (member.PronounsFor(ctx) is {} pronouns&& !string.IsNullOrWhiteSpace(pronouns)) { eb.AddField("Pronouns", pronouns.Truncate(1024), true); } if (member.MessageCountFor(ctx) is {} count&& count > 0) { eb.AddField("Message Count", member.MessageCount.ToString(), true); } if (member.HasProxyTags) { eb.AddField("Proxy Tags", member.ProxyTagsString("\n").Truncate(1024), true); } // --- For when this gets added to the member object itself or however they get added // if (member.LastMessage != null && member.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last message:" FormatTimestamp(DiscordUtils.SnowflakeToInstant(m.LastMessage.Value))); // if (member.LastSwitchTime != null && m.MetadataPrivacy.CanAccess(ctx)) eb.AddField("Last switched in:", FormatTimestamp(member.LastSwitchTime.Value)); // if (!member.Color.EmptyOrNull() && member.ColorPrivacy.CanAccess(ctx)) eb.AddField("Color", $"#{member.Color}", true); if (!member.Color.EmptyOrNull()) { eb.AddField("Color", $"#{member.Color}", true); } if (groups.Count > 0) { // More than 5 groups show in "compact" format without ID var content = groups.Count > 5 ? string.Join(", ", groups.Select(g => g.DisplayName ?? g.Name)) : string.Join("\n", groups.Select(g => $"[`{g.Hid}`] **{g.DisplayName ?? g.Name}**")); eb.AddField($"Groups ({groups.Count})", content.Truncate(1000)); } if (member.DescriptionFor(ctx) is {} desc) { eb.AddField("Description", member.Description.NormalizeLineEndSpacing(), false); } return(eb.Build()); }
private async Task ExecuteClearModlogCommand(CommandContext ctx, DiscordMember member, Boolean silent, String reason) { if (!ctx.Member.HasPermission("insanitybot.moderation.clear_modlog")) { await ctx.RespondAsync(InsanityBot.LanguageConfig["insanitybot.error.lacking_permission"]); return; } if (silent) { await ctx.Message.DeleteAsync(); } String ClearReason = reason switch { "usedefault" => GetFormattedString(InsanityBot.LanguageConfig["insanitybot.moderation.no_reason_given"], ctx, member), _ => GetFormattedString(reason, ctx, member) }; DiscordEmbedBuilder embedBuilder = null, moderationEmbedBuilder = new DiscordEmbedBuilder { Title = "Modlog Cleared", Color = DiscordColor.SpringGreen, Footer = new DiscordEmbedBuilder.EmbedFooter { Text = "InsanityBot - ExaInsanity 2020-2021" } }; moderationEmbedBuilder.AddField("Moderator", ctx.Member.Mention, true) .AddField("Member", member.Mention, true) .AddField("Reason", ClearReason, true); try { File.Delete($"./data/{member.Id}/modlog.json"); embedBuilder = new DiscordEmbedBuilder { Description = GetFormattedString(InsanityBot.LanguageConfig["insanitybot.moderation.clear_modlog.success"], ctx, member), Color = DiscordColor.SpringGreen, Footer = new DiscordEmbedBuilder.EmbedFooter { Text = "InsanityBot - ExaInsanity 2020-2021" } }; _ = InsanityBot.HomeGuild.GetChannel(ToUInt64(InsanityBot.Config["insanitybot.identifiers.commands.modlog_channel_id"])) .SendMessageAsync(embed: moderationEmbedBuilder.Build()); } catch (Exception e) { embedBuilder = new DiscordEmbedBuilder { Description = GetFormattedString(InsanityBot.LanguageConfig["insanitybot.moderation.clear_modlog.failure"], ctx, member), Color = DiscordColor.Red, Footer = new DiscordEmbedBuilder.EmbedFooter { Text = "InsanityBot - ExaInsanity 2020-2021" } }; InsanityBot.Client.Logger.LogError($"{e}: {e.Message}"); } finally { if (!silent) { await ctx.RespondAsync(embed : embedBuilder.Build()); } } } }
public async Task <DiscordEmbed> CreateMessageInfoEmbed(DiscordClient client, FullMessage msg) { var ctx = LookupContext.ByNonOwner; var channel = await _client.GetChannel(msg.Message.Channel); var serverMsg = channel != null ? await channel.GetMessage(msg.Message.Mid) : null; // Need this whole dance to handle cases where: // - the user is deleted (userInfo == null) // - the bot's no longer in the server we're querying (channel == null) // - the member is no longer in the server we're querying (memberInfo == null) DiscordMember memberInfo = null; DiscordUser userInfo = null; if (channel != null) { memberInfo = await channel.Guild.GetMember(msg.Message.Sender); } if (memberInfo != null) { userInfo = memberInfo; // Don't do an extra request if we already have this info from the member lookup } else { userInfo = await client.GetUser(msg.Message.Sender); } // Calculate string displayed under "Sent by" string userStr; if (memberInfo != null && memberInfo.Nickname != null) { userStr = $"**Username:** {memberInfo.NameAndMention()}\n**Nickname:** {memberInfo.Nickname}"; } else if (userInfo != null) { userStr = userInfo.NameAndMention(); } else { userStr = $"*(deleted user {msg.Message.Sender})*"; } // Put it all together var eb = new DiscordEmbedBuilder() .WithAuthor(msg.Member.NameFor(ctx), iconUrl: DiscordUtils.WorkaroundForUrlBug(msg.Member.AvatarFor(ctx))) .WithDescription(serverMsg?.Content?.NormalizeLineEndSpacing() ?? "*(message contents deleted or inaccessible)*") .WithImageUrl(serverMsg?.Attachments?.FirstOrDefault()?.Url) .AddField("System", msg.System.Name != null ? $"{msg.System.Name} (`{msg.System.Hid}`)" : $"`{msg.System.Hid}`", true) .AddField("Member", $"{msg.Member.NameFor(ctx)} (`{msg.Member.Hid}`)", true) .AddField("Sent by", userStr, inline: true) .WithTimestamp(DiscordUtils.SnowflakeToInstant(msg.Message.Mid).ToDateTimeOffset()); var roles = memberInfo?.Roles?.ToList(); if (roles != null && roles.Count > 0) { var rolesString = string.Join(", ", roles.Select(role => role.Name)); eb.AddField($"Account roles ({roles.Count})", rolesString.Truncate(1024)); } return(eb.Build()); }