feat: Adds Discord integration for GM Pages (#2361)
This commit is contained in:
parent
217d641861
commit
76395b77ec
2 changed files with 234 additions and 2 deletions
222
Projects/UOContent/Engines/Help/PageDiscord.cs
Normal file
222
Projects/UOContent/Engines/Help/PageDiscord.cs
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
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<object>
|
||||||
|
{
|
||||||
|
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') =>
|
||||||
|
$"<t:{new DateTimeOffset(utcTime, TimeSpan.Zero).ToUnixTimeSeconds()}:{style}>";
|
||||||
|
|
||||||
|
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)]}...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -127,6 +127,7 @@ namespace Server.Engines.Help
|
||||||
public static void Configure()
|
public static void Configure()
|
||||||
{
|
{
|
||||||
CommandSystem.Register("Pages", AccessLevel.Counselor, Pages_OnCommand);
|
CommandSystem.Register("Pages", AccessLevel.Counselor, Pages_OnCommand);
|
||||||
|
PageDiscord.Configure();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CheckAllowedToPage(Mobile from)
|
public static bool CheckAllowedToPage(Mobile from)
|
||||||
|
|
@ -174,6 +175,8 @@ namespace Server.Engines.Help
|
||||||
m_KeyedByHandler[value] = entry;
|
m_KeyedByHandler[value] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = PageDiscord.SendPageHandlerUpdateAsync(entry, old, value);
|
||||||
|
|
||||||
if (old == null || value == null)
|
if (old == null || value == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -214,6 +217,11 @@ namespace Server.Engines.Help
|
||||||
|
|
||||||
e.Stop();
|
e.Stop();
|
||||||
|
|
||||||
|
if (e.Handler != null)
|
||||||
|
{
|
||||||
|
_ = PageDiscord.SendPageCompletedAsync(e);
|
||||||
|
}
|
||||||
|
|
||||||
List.Remove(e);
|
List.Remove(e);
|
||||||
m_KeyedBySender.Remove(e.Sender);
|
m_KeyedBySender.Remove(e.Sender);
|
||||||
|
|
||||||
|
|
@ -261,7 +269,7 @@ namespace Server.Engines.Help
|
||||||
if (!isStaffOnline)
|
if (!isStaffOnline)
|
||||||
{
|
{
|
||||||
entry.Sender.SendMessage(
|
entry.Sender.SendMessage(
|
||||||
"We are sorry, but no staff members are currently available to assist you. Your page will remain in the queue until one becomes available, or until you cancel it manually."
|
"We are sorry, but no staff members are currently available to assist you. Your page will remain in the queue until one becomes available, or until you cancel it manually."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -270,7 +278,9 @@ namespace Server.Engines.Help
|
||||||
Email.SendQueueEmail(entry, GetPageTypeName(entry.Type));
|
Email.SendQueueEmail(entry, GetPageTypeName(entry.Type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = PageDiscord.SendPageNotificationAsync(entry);
|
||||||
|
|
||||||
HelpEvents.InvokePageEnqueued(entry);
|
HelpEvents.InvokePageEnqueued(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue