using System; using System.Net.Http; using System.Threading.Tasks; using Server.Logging; using System.Collections.Generic; using System.Net.Http.Json; namespace Server.Engines.Help; public static class PageDiscord { private static HttpClient _httpClient; private static readonly ILogger _logger = LogFactory.GetLogger(typeof(PageDiscord)); private static string _webhookUrl; public static void Configure() { _webhookUrl = ServerConfiguration.GetOrUpdateSetting("pages.discordWebhookUrl", null); if (IsEnabled) { _logger.Information("Relaying pages to discord enabled."); } } public static bool IsEnabled => !string.IsNullOrEmpty(_webhookUrl); public static async ValueTask SendPageNotificationAsync(PageEntry entry) { if (!IsEnabled) { return; } var pageTypeName = PageQueue.GetPageTypeName(entry.Type); var locationInfo = $"{entry.PageLocation} ({entry.PageMap?.Name ?? "Unknown"})"; var fieldsList = new List { new { name = "Player", value = entry.Sender?.Name ?? "Unknown", inline = true }, new { name = "Account", value = entry.Sender?.Account?.Username ?? "Unknown", inline = true }, new { name = "Page Type", value = pageTypeName, inline = true }, new { name = "Location", value = locationInfo, inline = true }, new { name = "Time Sent", value = FormatDiscordTimestamp(entry.Sent), inline = true }, new { name = "Queue Position", value = $"#{PageQueue.List.Count}", inline = true }, new { name = "Message", value = TruncateMessage(entry.Message), inline = false } }; if (entry.SpeechLog != null && entry.SpeechLog.Count > 0) { fieldsList.Add(new { name = "Speech Log Available", value = $"Contains {entry.SpeechLog.Count} speech entries", inline = false }); } var embed = new { title = $"New {pageTypeName} Page", color = GetPageTypeColor(entry.Type), fields = fieldsList.ToArray(), footer = new { text = "UO Page System" }, timestamp = entry.Sent.ToString("o") }; var payload = new { username = "UO Pages", embeds = new[] { embed } }; try { _httpClient ??= new HttpClient(); await _httpClient.PostAsJsonAsync(_webhookUrl, payload); _logger.Debug("Discord page notification sent for {Player} - {PageType}", entry.Sender?.Name, pageTypeName); } catch (Exception ex) { _logger.Error(ex, "Discord page webhook failed for {Player} - {PageType}", entry.Sender?.Name, entry.Type); } } public static async ValueTask SendPageHandlerUpdateAsync(PageEntry entry, Mobile oldHandler, Mobile newHandler) { if (!IsEnabled || oldHandler != null && newHandler == null && PageQueue.IndexOf(entry) == -1) { return; } var pageTypeName = PageQueue.GetPageTypeName(entry.Type); string actionText; int color; if (oldHandler == null && newHandler != null) { actionText = $"🛠️ **{newHandler.Name}** is now handling the page"; color = 0x00FF00; // green } else if (oldHandler != null && newHandler == null) { actionText = $"❌ **{oldHandler.Name}** stopped handling the page"; color = 0xFF0000; // red } else { return; } var embed = new { title = $"{pageTypeName} Page Update", description = actionText, color, fields = new[] { new { name = "Player", value = entry.Sender?.Name ?? "Unknown", inline = true }, new { name = "Page Type", value = pageTypeName, inline = true }, new { name = "Queue Position", value = $"#{PageQueue.IndexOf(entry) + 1}", inline = true } }, footer = new { text = "UO Page System" }, timestamp = Core.Now.ToString("o") }; var payload = new { username = "UO Pages", embeds = new[] { embed } }; try { _httpClient ??= new HttpClient(); await _httpClient.PostAsJsonAsync(_webhookUrl, payload); _logger.Debug("Discord page handler update sent for {Player} - {PageType}", entry.Sender?.Name, pageTypeName); } catch (Exception ex) { _logger.Error(ex, "Discord page handler update webhook failed for {Player} - {PageType}", entry.Sender?.Name, entry.Type); } } public static async ValueTask SendPageCompletedAsync(PageEntry entry) { if (!IsEnabled) { return; } var pageTypeName = PageQueue.GetPageTypeName(entry.Type); var handlerName = entry.Handler?.Name ?? "Staff"; var elapsed = Core.Now - entry.Sent; var elapsedText = elapsed.TotalMinutes < 1 ? "< 1 minute" : $"{elapsed.TotalMinutes:F0} minutes"; var embed = new { title = $"✅ {pageTypeName} Page Completed", color = 0x00AA00, // dark green fields = new[] { new { name = "Player", value = entry.Sender?.Name ?? "Unknown", inline = true }, new { name = "Handled By", value = handlerName, inline = true }, new { name = "Response Time", value = elapsedText, inline = true } }, footer = new { text = "UO Page System" }, timestamp = Core.Now.ToString("yyyy-MM-ddTHH:mm:ssZ") }; var payload = new { username = "UO Pages", embeds = new[] { embed } }; try { _httpClient ??= new HttpClient(); await _httpClient.PostAsJsonAsync(_webhookUrl, payload); _logger.Debug("Discord page completed notification sent for {Player} - {PageType}", entry.Sender?.Name, pageTypeName); } catch (Exception ex) { _logger.Error(ex, "Discord page completed webhook failed for {Player} - {PageType}", entry.Sender?.Name, entry.Type); } } private static int GetPageTypeColor(PageType type) => type switch { PageType.Bug => 0xFF0000, // red PageType.Stuck => 0xFFFF00, // yellow PageType.Account => 0x0000FF, // blue PageType.Question => 0x00FFFF, // cyan PageType.Suggestion => 0x00FF00, // green PageType.VerbalHarassment => 0xFF00FF, // magenta PageType.PhysicalHarassment => 0x800080, // purple _ => 0x888888 // gray for other }; private static string FormatDiscordTimestamp(DateTime utcTime, char style = 'f') => $""; private static string TruncateMessage(string message) { if (string.IsNullOrEmpty(message)) { return "*(no message)*"; } const int maxLength = 1000; // apparently the discord field limit is 1024 return message.Length <= maxLength ? message : $"{message[..(maxLength - 3)]}..."; } }