using System.Text.Json; using Microsoft.EntityFrameworkCore; using RadicadeServer.DB; using RadicadeServer.RecNet.Enums.Moderation; using RadicadeServer.Discord.Webhooks; namespace RadicadeServer.Radicade.Discord; public static class AdminActionDiscord { public static void LogAction(string action, string? admin, string? details) { if (string.IsNullOrWhiteSpace(WebhookConfig.AdminActivityUrl)) return; if (string.IsNullOrWhiteSpace(action)) return; ReportDiscord.SendAsync(WebhookConfig.AdminActivityUrl, $"🛠️ Admin Action: {action}", new DiscordPayload { Title = $"Admin Action: {action}", ThumbnailUrl = WebhookImages.AdminActivity, Color = 0x5865F2, Fields = new List { new() { Name = "Action", Value = Truncate(action, 200), Inline = true }, new() { Name = "Admin", Value = Truncate(string.IsNullOrWhiteSpace(admin) ? "Unknown" : admin, 100), Inline = true }, new() { Name = "Details", Value = Truncate(string.IsNullOrWhiteSpace(details) ? "—" : details, 900), Inline = false } }, TimestampUtc = DateTime.UtcNow.ToString("O") }); } public static async Task LogBanAsync(RecDBContext db, long bannedPlayerId, string? bannedBy, bool causedByReports, string? reason, ReportCategory category, DateTime? expiry, bool isModerator, string? banMethod = null) { if (string.IsNullOrWhiteSpace(WebhookConfig.InGameBansUrl)) return; var playerName = bannedPlayerId.ToString(); try { var profile = await db.Profiles.AsNoTracking().FirstOrDefaultAsync(x => x.accountId == bannedPlayerId); if (profile != null) playerName = string.IsNullOrWhiteSpace(profile.displayName) ? (profile.username ?? bannedPlayerId.ToString()) : profile.displayName; } catch { } var durationText = FormatDuration(expiry); var fields = new List { new() { Name = "Banned Player", Value = $"{playerName} ({bannedPlayerId})", Inline = true }, new() { Name = "Banned By", Value = Truncate(string.IsNullOrWhiteSpace(bannedBy) ? "Unknown" : bannedBy, 100), Inline = true }, new() { Name = "Caused by Reports", Value = causedByReports ? "Yes" : "No", Inline = true }, new() { Name = "Category", Value = category.ToString(), Inline = true }, new() { Name = "Duration", Value = durationText, Inline = true }, new() { Name = "Is Moderator", Value = isModerator ? "Yes" : "No", Inline = true }, new() { Name = "Reason", Value = Truncate(string.IsNullOrWhiteSpace(reason) ? "—" : reason, 900), Inline = false } }; if (!string.IsNullOrWhiteSpace(banMethod)) { fields.Insert(5, new DiscordField { Name = "Ban Method", Value = Truncate(banMethod, 100), Inline = true }); } ReportDiscord.SendAsync(WebhookConfig.InGameBansUrl, $"🚫 Player Banned: {playerName}", new DiscordPayload { Title = $"Player Banned: {playerName}", ThumbnailUrl = WebhookImages.InGameBan, Color = 0xE74C3C, Fields = fields, TimestampUtc = DateTime.UtcNow.ToString("O") }); } private static string FormatDuration(DateTime? expiry) { if (expiry == null || expiry.Value == DateTime.MaxValue) return "Permanent"; var remaining = expiry.Value - DateTime.UtcNow; if (remaining <= TimeSpan.Zero) return $"Expires {expiry.Value:yyyy-MM-dd HH:mm} UTC"; var parts = new List(); if (remaining.Days > 0) parts.Add($"{remaining.Days}d"); if (remaining.Hours > 0) parts.Add($"{remaining.Hours}h"); if (remaining.Minutes > 0) parts.Add($"{remaining.Minutes}m"); if (remaining.Seconds > 0 || parts.Count == 0) parts.Add($"{remaining.Seconds}s"); return $"{string.Join(" ", parts)} (until {expiry.Value:yyyy-MM-dd HH:mm} UTC)"; } private static string Truncate(string value, int max) { if (string.IsNullOrEmpty(value)) return ""; if (value.Length <= max) return value; var cut = max; if (char.IsHighSurrogate(value[cut - 1]) && cut < value.Length && char.IsLowSurrogate(value[cut])) cut--; return value[..cut]; } }