using Discord.WebSocket; using System.Text; namespace MinecraftDiscordBot; public class RefinedStorageService : CommandRouter { private readonly ITaskWaitSource _taskSource; public override string HelpTextPrefix => "!rs "; public RefinedStorageService(ITaskWaitSource taskSource) : base() => _taskSource = taskSource; public override Task FallbackHandler(SocketUserMessage message, string method, string[] parameters, CancellationToken ct) => Task.FromResult(ResponseType.AsString($"The RS system has no command '{method}'!")); public override Task RootAnswer(SocketUserMessage message, CancellationToken ct) => Task.FromResult(ResponseType.AsString("The RS system is online!")); private async Task Method(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; } private const string CmdEnergyUsage = "energyusage"; private const string CmdEnergyStorage = "energystorage"; private const string CmdListItems = "listitems"; private const string CmdItemName = "itemname"; private const string CmdListFluids = "listfluids"; private const string CmdCraftItem = "craft"; private const string CmdGetItem = "getitem"; public async Task GetEnergyUsageAsync(CancellationToken ct) => await Method(CmdEnergyUsage, int.Parse, ct); public async Task GetEnergyStorageAsync(CancellationToken ct) => await Method(CmdEnergyStorage, int.Parse, ct); public async Task> ListItemsAsync(CancellationToken ct) => await Method(CmdListItems, ConnectedComputer.Deserialize>(), ct); public async Task> ListFluidsAsync(CancellationToken ct) => await Method(CmdListFluids, ConnectedComputer.Deserialize>(), ct); public async Task GetItemData(string itemid, CancellationToken ct) => await Method(CmdGetItem, ConnectedComputer.Deserialize(), ct, new() { ["name"] = itemid }); public async Task CraftItem(string itemid, int amount, CancellationToken ct) => await Method(CmdCraftItem, ConnectedComputer.Deserialize(), ct, new() { ["name"] = itemid, ["count"] = amount }); private Task> FilterItems(SocketUserMessage message, IEnumerable filters, CancellationToken ct) => FilterItems(message, filters.Select(ItemFilter.Parse), ct); private async Task> FilterItems(SocketUserMessage message, IEnumerable filters, CancellationToken ct) { var items = Items?.ToList().AsEnumerable(); if (items is null) items = (await RefreshItemList(ct)).ToList(); foreach (var filter in filters) items = items.Where(filter.MatchItem); return items.ToList(); } private async Task> RefreshItemList(CancellationToken ct) { var response = await ListItemsAsync(ct); lock (_itemLock) { Items = response.OrderByDescending(i => i.Amount).ToList(); return Items; } } private List? Items; private readonly object _itemLock = new(); [CommandHandler(CmdEnergyStorage, HelpText = "Get the amount of energy stored in the RS system.")] public async Task HandleEnergyStorage(SocketUserMessage message, string[] parameters, CancellationToken ct) => ResponseType.AsString($"Refined Storage system stores {await GetEnergyStorageAsync(ct)} RF/t"); [CommandHandler(CmdEnergyUsage, HelpText = "Get the amount of energy used by the RS system.")] public async Task HandleEnergyUsage(SocketUserMessage message, string[] parameters, CancellationToken ct) => ResponseType.AsString($"Refined Storage system currently uses {await GetEnergyUsageAsync(ct)} RF/t"); [CommandHandler(CmdCraftItem, HelpText = "Craft a specific item given an item ID and optionally an amount.")] public async Task HandleCraftItem(SocketUserMessage message, string[] parameters, CancellationToken ct) { var amount = 1; string itemid; if (parameters.Length is 1 or 2) { itemid = parameters[0]; if (parameters.Length is 2) if (int.TryParse(parameters[1], out var value)) amount = value; else return ResponseType.AsString($"I expected an amount to craft, not '{parameters[1]}'!"); } else return parameters.Length is < 1 ? ResponseType.AsString("You have to give me at least an item name!") : parameters.Length is > 2 ? ResponseType.AsString("Yo, those are way too many arguments! I want only item name and maybe an amount!") : throw new InvalidOperationException($"Forgot to match parameter length {parameters.Length}!"); return await CraftItem(itemid, amount, ct) ? ResponseType.AsString($"Alright, I'm starting to craft {amount} {itemid}.") : ResponseType.AsString($"Nope, that somehow doesn't work!"); } [CommandHandler(CmdGetItem, HelpText = "Get information about a specific item.")] public async Task HandleGetItemData(SocketUserMessage message, string[] parameters, CancellationToken ct) { var amount = 1; string itemid; if (parameters.Length is 1 or 2) { itemid = parameters[0]; if (parameters.Length is 2) if (int.TryParse(parameters[1], out var value)) amount = value; else return ResponseType.AsString($"I expected an amount to craft, not '{parameters[1]}'!"); } else return parameters.Length is < 1 ? ResponseType.AsString("You have to give me at least an item name!") : parameters.Length is > 2 ? ResponseType.AsString("Yo, those are way too many arguments! I want only item name and maybe an amount!") : throw new InvalidOperationException($"Forgot to match parameter length {parameters.Length}!"); var data = await GetItemData(itemid, ct); return ResponseType.AsString(data.ToString()); } [CommandHandler(CmdItemName, HelpText = "Filter items by name.")] public async Task HandleItemName(SocketUserMessage message, string[] parameters, CancellationToken ct) { if (parameters.Length < 2) return ResponseType.AsString($"Usage: {CmdItemName} filters..."); else { var items = await FilterItems(message, parameters[1..], ct); var sb = new StringBuilder(); sb.AppendLine("Did you mean:"); sb.AppendJoin("\n", items.Select(i => i.ToString())); return ResponseType.AsString(sb.ToString()); } } [CommandHandler(CmdListFluids, HelpText = "Gets a list of fluids that are currently stored in the RS system.")] public async Task HandleFluidListing(SocketUserMessage message, string[] parameters, CancellationToken ct) { var sb = new StringBuilder(); sb.Append("The Refined Storage system stores those fluids:"); var fluids = await ListFluidsAsync(ct); foreach (var fluid in fluids.OrderByDescending(i => i.Amount)) if (fluid.Amount > 10000) sb.AppendFormat("\n{0:n2} B of {1}", fluid.Amount / 1000.0f, fluid.DisplayName); else sb.AppendFormat("\n{0:n0} mB of {1}", fluid.Amount, fluid.DisplayName); return ResponseType.AsString(sb.ToString()); } [CommandHandler(CmdListItems, HelpText = "Gets a list of items that are currently stored in the RS system.")] public async Task HandleItemListing(SocketUserMessage message, string[] parameters, CancellationToken ct) { var sb = new StringBuilder(); sb.Append("The Refined Storage system currently stores these items:"); var items = await RefreshItemList(ct); lock (_itemLock) { int taken = 0; foreach (var item in items) { if (sb.Length > 500) break; sb.AppendFormat("\n{0:n0}x {1}", item.Amount, item.DisplayName); taken++; } if (items.Count > taken) sb.AppendFormat("\nand {0} more items.", items.Skip(taken).Sum(i => i.Amount)); } return ResponseType.AsString(sb.ToString()); } }