mcdiscordbot/MinecraftDiscordBot/Services/RefinedStorageService.cs
Michael Chen 9fd50ee01e
server:
Fixed cli help texts
Added administrator options for critical methods
Added result state for client and server specific errors
Redirect root to help text
Fixed fingerprint error, fingerprint must be case sensitive
Re-Added online messages
Added typing trigger for discord bot messages
client:
fixed chunkString for empty results preemtive
wrap error objects for server messages
both:
added raw lua RS Bridge command entry
2022-01-17 15:25:03 +01:00

167 lines
9.3 KiB
C#

using Discord.WebSocket;
using MinecraftDiscordBot.Commands;
using MinecraftDiscordBot.Models;
using System.Text;
using System.Text.RegularExpressions;
namespace MinecraftDiscordBot.Services;
public class RefinedStorageService : CommandRouter {
private readonly ITaskWaitSource _taskSource;
private readonly IUserRoleManager _roleManager;
public override string HelpTextPrefix => "!rs ";
public RefinedStorageService(ITaskWaitSource taskSource, IUserRoleManager roleManager) : base() {
_taskSource = taskSource;
_roleManager = roleManager;
}
public override Task<ResponseType> FallbackHandler(SocketUserMessage message, string method, string[] parameters, CancellationToken ct)
=> throw new ReplyException($"The RS system has no command '{method}'!");
private async Task<T> Method<T>(string methodName, Func<string, T> parser, CancellationToken ct, Dictionary<string, object>? 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";
private const string CmdCommand = "command";
public async Task<int> GetEnergyUsageAsync(CancellationToken ct) => await Method(CmdEnergyUsage, int.Parse, ct);
public async Task<int> GetEnergyStorageAsync(CancellationToken ct) => await Method(CmdEnergyStorage, int.Parse, ct);
public async Task<IEnumerable<Item>> ListItemsAsync(CancellationToken ct) => await Method(CmdListItems, RootCommandService.Deserialize<IEnumerable<Item>>(), ct);
public async Task<IEnumerable<Fluid>> ListFluidsAsync(CancellationToken ct) => await Method(CmdListFluids, RootCommandService.Deserialize<IEnumerable<Fluid>>(), ct);
public async Task<Item> GetItemData(string itemid, CancellationToken ct) => await Method(CmdGetItem, RootCommandService.Deserialize<Item>(), ct, new() {
["name"] = itemid
});
public async Task<Item> GetItemData(Md5Hash fingerprint, CancellationToken ct) => await Method(CmdGetItem, RootCommandService.Deserialize<Item>(), ct, new() {
["fingerprint"] = fingerprint.ToString()
});
public async Task<bool> CraftItem(string itemid, int amount, CancellationToken ct) => await Method(CmdCraftItem, RootCommandService.Deserialize<bool>(), ct, new() {
["name"] = itemid,
["count"] = amount
});
public async Task<LuaPackedArray> RawCommand(string command, CancellationToken ct) => await Method(CmdCommand, LuaPackedArray.Deserialize, ct, new() {
["command"] = command
});
private Task<IEnumerable<Item>> FilterItems(SocketUserMessage message, IEnumerable<string> filters, CancellationToken ct)
=> FilterItems(message, filters.Select(ItemFilter.Parse), ct);
private async Task<IEnumerable<Item>> FilterItems(SocketUserMessage message, IEnumerable<ItemFilter> 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<List<Item>> RefreshItemList(CancellationToken ct) {
var response = await ListItemsAsync(ct);
lock (_itemLock) {
Items = response.OrderByDescending(i => i.Amount).ToList();
return Items;
}
}
private List<Item>? Items;
private readonly object _itemLock = new();
[CommandHandler(CmdEnergyStorage, HelpText = "Get the amount of energy stored in the RS system.")]
public async Task<ResponseType> 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<ResponseType> 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<ResponseType> 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)
amount = int.TryParse(parameters[1], out var value)
? value
: throw new ReplyException($"I expected an amount to craft, not '{parameters[1]}'!");
} else return parameters.Length is < 1
? throw new ReplyException("You have to give me at least an item name!")
: parameters.Length is > 2
? throw new ReplyException("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<ResponseType> HandleGetItemData(SocketUserMessage message, string[] parameters, CancellationToken ct) {
string itemid;
if (parameters.Length is not 1) throw new ReplyException($"I only want one name or fingerprint to search for, you gave me {parameters.Length} arguments!");
itemid = parameters[0];
var item = await (Md5Hash.TryParse(itemid, out var fingerprint)
? GetItemData(fingerprint, ct)
: GetItemData(itemid, ct));
var sb = new StringBuilder();
sb.Append($"We currently have {item.Amount:n0} {item.CleanDisplayName}!");
if (item.Tags is not null and var tags) {
sb.AppendLine("\nThis item has the following tags:");
sb.AppendJoin('\n', tags.Select(tag => $"- {tag}"));
}
sb.Append($"\nRefer to this item with fingerprint {item.Fingerprint}");
return ResponseType.AsString(sb.ToString());
}
[CommandHandler(CmdItemName, HelpText = "Filter items by name.")]
public async Task<ResponseType> 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<ResponseType> 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(CmdCommand, HelpText = "Runs a raw command on the RS system.")]
public async Task<ResponseType> HandleRawCommand(SocketUserMessage message, string[] parameters, CancellationToken ct) {
_roleManager.RequireAdministrator(message.Author.Id, "You are not authorized to run raw commands on this instance!");
var command = string.Join(' ', parameters);
var response = await RawCommand(command, ct);
return ResponseType.AsString(response.ToString());
}
[CommandHandler(CmdListItems, HelpText = "Gets a list of items that are currently stored in the RS system.")]
public async Task<ResponseType> 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) {
var 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());
}
}