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.
68 lines
2.5 KiB
C#
68 lines
2.5 KiB
C#
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);
|
|
}
|