Launcher milestone 2: player list, kick/ban, live stats
Adds an admin-protocol client (token-authed TCP JSON on 127.0.0.1:AdminPort, reusing the server's .admin-token) that polls connected players and status every 3s, plus Kick/Ban on the selected player and a players/uptime readout.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CommonwealthOnline.Host.Services;
|
||||
|
||||
// Speaks the server's token-authenticated admin protocol on 127.0.0.1:AdminPort:
|
||||
// read .admin-token, send {..,"adminToken"}\n, read one newline-terminated JSON reply.
|
||||
public sealed class AdminClient
|
||||
{
|
||||
private const int MaxResponseBytes = 1_000_000;
|
||||
|
||||
private readonly int _port;
|
||||
private readonly string _tokenPath;
|
||||
|
||||
public AdminClient(int adminPort, string tokenPath)
|
||||
{
|
||||
_port = adminPort;
|
||||
_tokenPath = tokenPath;
|
||||
}
|
||||
|
||||
public async Task<JsonObject?> SendAsync(JsonObject request, CancellationToken ct = default)
|
||||
{
|
||||
var token = (await File.ReadAllTextAsync(_tokenPath, ct).ConfigureAwait(false)).Trim();
|
||||
var authenticated = (JsonObject)request.DeepClone();
|
||||
authenticated["adminToken"] = token;
|
||||
var payload = JsonSerializer.SerializeToUtf8Bytes(authenticated);
|
||||
|
||||
using var client = new TcpClient();
|
||||
await client.ConnectAsync(IPAddress.Loopback, _port, ct).ConfigureAwait(false);
|
||||
var stream = client.GetStream();
|
||||
await stream.WriteAsync(payload, ct).ConfigureAwait(false);
|
||||
await stream.WriteAsync(new byte[] { (byte)'\n' }, ct).ConfigureAwait(false);
|
||||
|
||||
using var buffer = new MemoryStream();
|
||||
var one = new byte[1];
|
||||
while (buffer.Length < MaxResponseBytes)
|
||||
{
|
||||
var read = await stream.ReadAsync(one, ct).ConfigureAwait(false);
|
||||
if (read == 0 || one[0] == (byte)'\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.WriteByte(one[0]);
|
||||
}
|
||||
|
||||
return JsonNode.Parse(buffer.ToArray()) as JsonObject;
|
||||
}
|
||||
|
||||
public Task<JsonObject?> StatusAsync(CancellationToken ct = default) =>
|
||||
SendAsync(new JsonObject { ["cmd"] = "status" }, ct);
|
||||
|
||||
public Task<JsonObject?> ClientsAsync(CancellationToken ct = default) =>
|
||||
SendAsync(new JsonObject { ["cmd"] = "clients" }, ct);
|
||||
|
||||
public Task<JsonObject?> KickAsync(uint playerId, string reason, CancellationToken ct = default) =>
|
||||
SendAsync(new JsonObject { ["cmd"] = "kick", ["playerId"] = playerId, ["reason"] = reason }, ct);
|
||||
|
||||
public Task<JsonObject?> BanAsync(uint playerId, string reason, CancellationToken ct = default) =>
|
||||
SendAsync(new JsonObject { ["cmd"] = "ban", ["playerId"] = playerId, ["reason"] = reason }, ct);
|
||||
}
|
||||
Reference in New Issue
Block a user