Skip to content

Commit 9f74d73

Browse files
committed
feat: add /ping and /status diagnostics commands
1 parent 7e6680a commit 9f74d73

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Diagnostics;
2+
using System.Globalization;
3+
using Discord;
4+
using Discord.Interactions;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using RustPlusBot.Features.Commands.Formatting;
7+
using RustPlusBot.Features.Commands.Hosting;
8+
using RustPlusBot.Persistence.Connections;
9+
using RustPlusBot.Persistence.Servers;
10+
11+
namespace RustPlusBot.Features.Commands.Modules;
12+
13+
/// <summary>Read-only diagnostics: /ping and /status.</summary>
14+
/// <param name="scopeFactory">Creates a short-lived DI scope per interaction.</param>
15+
public sealed class DiagnosticsModule(IServiceScopeFactory scopeFactory)
16+
: InteractionModuleBase<SocketInteractionContext>
17+
{
18+
/// <summary>Reports Discord gateway latency and the REST ack round-trip.</summary>
19+
[SlashCommand("ping", "Show the bot's Discord latency")]
20+
public async Task PingAsync()
21+
{
22+
var stopwatch = Stopwatch.StartNew();
23+
await DeferAsync(ephemeral: true).ConfigureAwait(false);
24+
stopwatch.Stop();
25+
26+
var text = string.Create(CultureInfo.InvariantCulture,
27+
$"Pong! Gateway: {Context.Client.Latency} ms · Response: {stopwatch.ElapsedMilliseconds} ms");
28+
await FollowupAsync(text, ephemeral: true).ConfigureAwait(false);
29+
}
30+
31+
/// <summary>Shows uptime, latency, per-server connection status, and counts.</summary>
32+
[SlashCommand("status", "Show bot health and connection status")]
33+
public async Task StatusAsync()
34+
{
35+
if (Context.Guild is null)
36+
{
37+
await RespondAsync("This command must be used in a server.", ephemeral: true).ConfigureAwait(false);
38+
return;
39+
}
40+
41+
await DeferAsync(ephemeral: true).ConfigureAwait(false);
42+
var scope = scopeFactory.CreateAsyncScope();
43+
await using (scope.ConfigureAwait(false))
44+
{
45+
var uptime = scope.ServiceProvider.GetRequiredService<BotUptime>();
46+
var servers = scope.ServiceProvider.GetRequiredService<IServerService>();
47+
var connections = scope.ServiceProvider.GetRequiredService<IConnectionStore>();
48+
49+
var known = await servers.ListAsync(Context.Guild.Id).ConfigureAwait(false);
50+
51+
var embed = new EmbedBuilder()
52+
.WithTitle("Bot status")
53+
.AddField("Uptime", DurationFormat.Compact(uptime.Elapsed), inline: true)
54+
.AddField("Gateway latency",
55+
string.Create(CultureInfo.InvariantCulture, $"{Context.Client.Latency} ms"), inline: true)
56+
.AddField("Guilds",
57+
Context.Client.Guilds.Count.ToString(CultureInfo.InvariantCulture), inline: true)
58+
.AddField("Servers (this guild)",
59+
known.Count.ToString(CultureInfo.InvariantCulture), inline: true);
60+
61+
foreach (var server in known)
62+
{
63+
var state = await connections.GetStateAsync(Context.Guild.Id, server.Id).ConfigureAwait(false);
64+
var line = state is null
65+
? "unknown"
66+
: string.Create(CultureInfo.InvariantCulture,
67+
$"{state.Status} · {state.PlayerCount?.ToString(CultureInfo.InvariantCulture) ?? "?"} players");
68+
embed.AddField(server.Name, line);
69+
}
70+
71+
await FollowupAsync(ephemeral: true, embed: embed.Build()).ConfigureAwait(false);
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)