using Discord.WebSocket; using Fleck; using MinecraftDiscordBot.Commands; using MinecraftDiscordBot.Models; using Newtonsoft.Json; using System.Linq; namespace MinecraftDiscordBot.Services; public delegate Task HandleCommandDelegate(SocketUserMessage message, string[] parameters, CancellationToken ct); public delegate Task HandleCommandDelegate(SocketUserMessage message, string[] parameters, CancellationToken ct); public class RootCommandService : CommandRouter, ITaskWaitSource { protected IWebSocketConnection? _socketField; public override string HelpTextPrefix => "!"; public RootCommandService(IUserRoleManager roleManager) : base() { RefinedStorage = new RefinedStorageService(this, roleManager); Players = new PlayerDetectorService(this); Chat = new ChatBoxService(this); } public static async Task Method(ITaskWaitSource taskSource, string methodName, Func parser, CancellationToken ct, Dictionary? parameters = null) { var waiter = taskSource.GetWaiter(parser, ct); await taskSource.Send(new RequestMessage(waiter.ID, methodName, parameters)); return await waiter.Task; } public event EventHandler? ChatMessageReceived; public event EventHandler? PlayerStatusChanged; public event EventHandler? PeripheralAttached; public event EventHandler? PeripheralDetached; public event EventHandler? SocketChanged; public IWebSocketConnection? Socket { get => _socketField; set { if (_socketField != value) { _socketField = value; if (value is not null) value.OnMessage = OnMessage; SocketChanged?.Invoke(this, value); } } } public RefinedStorageService RefinedStorage { get; } public PlayerDetectorService Players { get; } public ChatBoxService Chat { get; } private void OnMessage(string message) { switch (Message.Deserialize(message)) { case ChatEvent msg: ChatMessageReceived?.Invoke(this, msg); break; case PlayerStatusEvent msg: PlayerStatusChanged?.Invoke(this, msg); break; case PeripheralAttachEvent msg: PeripheralAttached?.Invoke(this, msg); break; case PeripheralDetachEvent msg: PeripheralDetached?.Invoke(this, msg); break; case ReplyMessage msg: IChunkWaiter? waiter; lock (_syncRoot) if (!_waits.TryGetValue(msg.AnswerId, out waiter)) { Program.LogWarningAsync("Socket", $"Invalid wait id '{msg.AnswerId}'!"); return; } waiter.SetResultState(msg.State); waiter.AddChunk(msg.Chunk, msg.Total, msg.Result); if (waiter.Finished || waiter.IsCancellationRequested) lock (_syncRoot) _waits.Remove(waiter.ID); break; default: Program.LogInfo(Program.WebSocketSource, $"Received unhandled message: {message}!"); break; } } public Task Send(string message) => (Socket ?? throw new ReplyException("Minecraft server is not available!")).Send(message); public Task Send(Message message) => Send(JsonConvert.SerializeObject(message)); private readonly object _syncRoot = new(); private readonly Dictionary _waits = new(); private readonly Random _rnd = new(); private int GetFreeId() { var attempts = 0; while (true) { var id = _rnd.Next(); if (!_waits.ContainsKey(id)) return id; Program.LogWarningAsync(Program.WebSocketSource, $"Could not get a free ID after {++attempts} attempts!"); } } public ChunkWaiter GetWaiter(Func resultParser, CancellationToken ct) { ChunkWaiter waiter; lock (_syncRoot) { waiter = new ChunkWaiter(GetFreeId(), resultParser, ct); _waits.Add(waiter.ID, waiter); } return waiter; } public Task> GetPeripherals(CancellationToken ct) => Method(this, "peripherals", Deserialize>(), ct); [CommandHandler("rs", HelpText = "Provides some commands for interacting with the Refined Storage system.")] public Task RefinedStorageHandler(SocketUserMessage message, string[] parameters, CancellationToken ct) => RefinedStorage.HandleCommand(message, parameters, ct); [CommandHandler("peripherals", HelpText = "Gets a list of peripherals that are attached.")] public async Task HandleGetPeripherals(SocketUserMessage message, string[] parameters, CancellationToken ct) => ResponseType.AsString(string.Join("\n", (await GetPeripherals(ct)).Values.Select(i => $"On side {i.Side}: {i.Type}"))); [CommandHandler("pd", HelpText = "Provides some commands for interacting with the Player Detector.")] public Task PlayerDetectorHandler(SocketUserMessage message, string[] parameters, CancellationToken ct) => Players.HandleCommand(message, parameters, ct); [CommandHandler("chat", HelpText = "Provides some commands for chatting.")] public Task ChatBoxHandler(SocketUserMessage message, string[] parameters, CancellationToken ct) => Chat.HandleCommand(message, parameters, ct); public static Func Deserialize() => msg => JsonConvert.DeserializeObject(msg) ?? throw new InvalidProgramException("Empty response!"); public override Task FallbackHandler(SocketUserMessage message, string method, string[] parameters, CancellationToken ct) => throw new ReplyException($"What the fuck do you mean by '{method}'?"); } public interface ITaskWaitSource { ChunkWaiter GetWaiter(Func resultParser, CancellationToken ct); Task Send(Message requestMessage); }