namespace Aeshnidae.DiscordRelay;
///
/// Chat / console commands added by this mod.
///
/// ModContainer scans the assembly for [CommandHandler] methods and registers them
/// when the mod is enabled (Meta.json "RegisterCommands": true), then removes them
/// again on Disable. Handlers must be public static and match ACE's CommandHandler
/// delegate exactly: (Session session, params string[] parameters).
///
/// All three are Admin-only: the status line names the Discord channels this server
/// pipes chat into, which is not something to hand out at Player access.
///
public static class Commands
{
[CommandHandler("discordrelay", AccessLevel.Admin, CommandHandlerFlag.None, 0,
"Show the Discord chat relay's status.",
"/discordrelay")]
public static void HandleStatus(Session session, params string[] parameters)
{
var container = Mod.Container;
var relay = Mod.Relay;
var text = new StringBuilder()
.AppendLine($"{Mod.Name} v{container?.Meta.Version ?? "?"} - {container?.Status.ToString() ?? "not loaded"}")
.AppendLine($"Enabled: {Mod.Settings.Enabled} batch: {Mod.Settings.BatchSeconds:0.#}s relay: {(relay is null ? "not running" : "running")}");
foreach (var (key, channel) in Mod.Settings.Channels.OrderBy(c => c.Key))
{
var state = !channel.Enabled ? "off"
: string.IsNullOrWhiteSpace(channel.WebhookUrl) ? "no webhook set"
: relay?.BrokenReason(key) ?? $"-> {MaskWebhook(channel.WebhookUrl)}";
var queued = relay?.QueueDepth(key) ?? 0;
text.AppendLine($" {key,-10} {state}{(queued > 0 ? $" ({queued} queued)" : "")}");
}
if (relay is not null)
{
text.AppendLine($"Queued {relay.Relayed}, dropped {relay.Dropped}, posts {relay.Posts}, failures {relay.Failures}");
text.AppendLine($"Last post: {(relay.LastPostUtc == default ? "never" : $"{(DateTime.UtcNow - relay.LastPostUtc).TotalSeconds:0}s ago")}");
if (!string.IsNullOrEmpty(relay.LastError))
text.AppendLine($"Last error: {relay.LastError}");
}
Reply(session, text.ToString());
}
[CommandHandler("discordrelay-test", AccessLevel.Admin, CommandHandlerFlag.None, 1,
"Post a test line to one relayed channel's Discord webhook.",
"/discordrelay-test ")]
public static void HandleTest(Session session, params string[] parameters)
{
var relay = Mod.Relay;
if (relay is null)
{
Reply(session, $"{Mod.Name} is not running.");
return;
}
var key = Mod.Settings.Channels.Keys.FirstOrDefault(k => string.Equals(k, parameters[0], StringComparison.OrdinalIgnoreCase));
if (key is null)
{
Reply(session, $"No channel called \"{parameters[0]}\" in Settings.json. Known: {string.Join(", ", Mod.Settings.Channels.Keys)}");
return;
}
var who = session?.Player?.Name ?? "the console";
relay.Submit(key, $"_Relay test for **{key}**, sent from {who}._");
Reply(session, $"Test line queued for {key}; it should appear within {Mod.Settings.BatchSeconds:0.#}s. Check /discordrelay if it does not.");
}
[CommandHandler("discordrelay-reload", AccessLevel.Admin, CommandHandlerFlag.None, 0,
"Restart the Discord relay, re-reading Settings.json.",
"/discordrelay-reload")]
public static void HandleReload(Session session, params string[] parameters)
{
var container = Mod.Container;
if (container is null)
{
Reply(session, $"{Mod.Name} is not loaded.");
return;
}
container.Restart();
Reply(session, $"{Mod.Name} restarted.");
}
///
/// A webhook URL is a credential, so the status line proves one is set without
/// printing it: host, the webhook id, and the token replaced.
///
private static string MaskWebhook(string url)
{
try
{
var uri = new Uri(url);
var segments = uri.Segments.Select(s => s.Trim('/')).Where(s => s.Length > 0).ToArray();
var id = segments.Length >= 2 ? segments[^2] : "?";
return $"{uri.Host}/.../{id}/****";
}
catch
{
return "set (unparseable URL)";
}
}
///
/// The client renders an embedded newline as a music note, so a multi-line
/// message has to go out as one SendMessage per line.
///
private static void Reply(Session? session, string message)
{
if (session?.Player is null)
{
ModManager.Log(message);
return;
}
foreach (var line in (message ?? "").Split('\n'))
{
var text = line.TrimEnd('\r');
if (!string.IsNullOrWhiteSpace(text))
session.Player.SendMessage(text);
}
}
}