diff --git a/Tools/BiblioTech/BaseCodexCommand.cs b/Tools/BiblioTech/BaseCodexCommand.cs new file mode 100644 index 0000000..737e0ec --- /dev/null +++ b/Tools/BiblioTech/BaseCodexCommand.cs @@ -0,0 +1,27 @@ +using BiblioTech.Options; +using CodexPlugin; +using Core; + +namespace BiblioTech +{ + public abstract class BaseCodexCommand : BaseDeploymentCommand + { + private readonly CoreInterface ci; + + public BaseCodexCommand(CoreInterface ci) + { + this.ci = ci; + } + + protected override async Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment) + { + var codexContainers = codexDeployment.CodexInstances.Select(c => c.Container).ToArray(); + + var group = ci.WrapCodexContainers(codexContainers); + + await Execute(context, group); + } + + protected abstract Task Execute(CommandContext context, ICodexNodeGroup codexGroup); + } +} diff --git a/Tools/BiblioTech/BaseCommand.cs b/Tools/BiblioTech/BaseCommand.cs index ee90df2..c68aaeb 100644 --- a/Tools/BiblioTech/BaseCommand.cs +++ b/Tools/BiblioTech/BaseCommand.cs @@ -23,9 +23,9 @@ namespace BiblioTech try { - await command.RespondAsync(StartingMessage, ephemeral: true); - await Invoke(new CommandContext(command, command.Data.Options)); - await command.DeleteOriginalResponseAsync(); + var context = new CommandContext(command, command.Data.Options); + await command.RespondAsync(StartingMessage, ephemeral: IsEphemeral(context)); + await Invoke(context); } catch (Exception ex) { @@ -34,6 +34,12 @@ namespace BiblioTech } } + private bool IsEphemeral(CommandContext context) + { + if (IsSenderAdmin(context.Command) && IsInAdminChannel(context.Command)) return false; + return true; + } + protected abstract Task Invoke(CommandContext context); protected bool IsSenderAdmin(SocketSlashCommand command) diff --git a/Tools/BiblioTech/BaseDeploymentCommand.cs b/Tools/BiblioTech/BaseDeploymentCommand.cs new file mode 100644 index 0000000..100c39f --- /dev/null +++ b/Tools/BiblioTech/BaseDeploymentCommand.cs @@ -0,0 +1,36 @@ +using BiblioTech.Options; +using CodexPlugin; + +namespace BiblioTech +{ + public abstract class BaseDeploymentCommand : BaseCommand + { + protected override async Task Invoke(CommandContext context) + { + var proceed = await OnInvoke(context); + if (!proceed) return; + + var deployments = Program.DeploymentFilesMonitor.GetDeployments(); + if (deployments.Length == 0) + { + await context.Followup("No deployments are currently available."); + return; + } + if (deployments.Length > 1) + { + await context.Followup("Multiple deployments are online. I don't know which one to pick!"); + return; + } + + var codexDeployment = deployments.Single(); + await ExecuteDeploymentCommand(context, codexDeployment); + } + + protected abstract Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment); + + protected virtual Task OnInvoke(CommandContext context) + { + return Task.FromResult(true); + } + } +} diff --git a/Tools/BiblioTech/BaseNetCommand.cs b/Tools/BiblioTech/BaseGethCommand.cs similarity index 50% rename from Tools/BiblioTech/BaseNetCommand.cs rename to Tools/BiblioTech/BaseGethCommand.cs index 4995cb8..37841b1 100644 --- a/Tools/BiblioTech/BaseNetCommand.cs +++ b/Tools/BiblioTech/BaseGethCommand.cs @@ -1,34 +1,22 @@ using BiblioTech.Options; using CodexContractsPlugin; +using CodexPlugin; using Core; using GethPlugin; namespace BiblioTech { - public abstract class BaseNetCommand : BaseCommand + public abstract class BaseGethCommand : BaseDeploymentCommand { private readonly CoreInterface ci; - public BaseNetCommand(CoreInterface ci) + public BaseGethCommand(CoreInterface ci) { this.ci = ci; } - protected override async Task Invoke(CommandContext context) + protected override async Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment) { - var deployments = Program.DeploymentFilesMonitor.GetDeployments(); - if (deployments.Length == 0) - { - await context.Followup("No deployments are currently available."); - return; - } - if (deployments.Length > 1) - { - await context.Followup("Multiple deployments are online. I don't know which one to pick!"); - return; - } - - var codexDeployment = deployments.Single(); var gethDeployment = codexDeployment.GethDeployment; var contractsDeployment = codexDeployment.CodexContractsDeployment; diff --git a/Tools/BiblioTech/Commands/AdminCommand.cs b/Tools/BiblioTech/Commands/AdminCommand.cs index 4de4985..6ed79a8 100644 --- a/Tools/BiblioTech/Commands/AdminCommand.cs +++ b/Tools/BiblioTech/Commands/AdminCommand.cs @@ -78,12 +78,12 @@ namespace BiblioTech.Commands var user = userOption.GetUser(context); if (user == null) { - await context.AdminFollowup("Failed to get user ID"); + await context.Followup("Failed to get user ID"); return; } Program.UserRepo.ClearUserAssociatedAddress(user); - await context.AdminFollowup("Done."); + await context.Followup("Done."); } } @@ -105,12 +105,20 @@ namespace BiblioTech.Commands var user = userOption.GetUser(context); if (user == null) { - await context.AdminFollowup("Failed to get user ID"); + await context.Followup("Failed to get user ID"); return; } - var report = Program.UserRepo.GetInteractionReport(user); - await context.AdminFollowup(string.Join(Environment.NewLine, report)); + var report = string.Join(Environment.NewLine, Program.UserRepo.GetInteractionReport(user)); + if (report.Length > 1900) + { + var filename = $"user-{user.Username}.log"; + await context.FollowupWithAttachement(filename, report); + } + else + { + await context.Followup(report); + } } } @@ -127,11 +135,12 @@ namespace BiblioTech.Commands if (!deployments.Any()) { - await context.AdminFollowup("No deployments available."); + await context.Followup("No deployments available."); return; } - await context.AdminFollowup($"Deployments: {string.Join(", ", deployments.Select(FormatDeployment))}"); + var nl = Environment.NewLine; + await context.Followup($"Deployments:{nl}{string.Join(nl, deployments.Select(FormatDeployment))}"); } private string FormatDeployment(CodexDeployment deployment) @@ -163,11 +172,11 @@ namespace BiblioTech.Commands var result = await Program.DeploymentFilesMonitor.DownloadDeployment(file); if (result) { - await context.AdminFollowup("Success!"); + await context.Followup("Success!"); } else { - await context.AdminFollowup("That didn't work."); + await context.Followup("That didn't work."); } } } @@ -194,11 +203,11 @@ namespace BiblioTech.Commands var result = Program.DeploymentFilesMonitor.DeleteDeployment(str); if (result) { - await context.AdminFollowup("Success!"); + await context.Followup("Success!"); } else { - await context.AdminFollowup("That didn't work."); + await context.Followup("That didn't work."); } } } @@ -227,11 +236,11 @@ namespace BiblioTech.Commands if (user != null) { - await context.AdminFollowup(Program.UserRepo.GetUserReport(user)); + await context.Followup(Program.UserRepo.GetUserReport(user)); } if (ethAddr != null) { - await context.AdminFollowup(Program.UserRepo.GetUserReport(ethAddr)); + await context.Followup(Program.UserRepo.GetUserReport(ethAddr)); } } } @@ -246,23 +255,23 @@ namespace BiblioTech.Commands this.ci = ci; } - protected async Task OnDeployment(CommandContext context, Func action) + protected async Task OnDeployment(CommandContext context, Func action) { var deployment = Program.DeploymentFilesMonitor.GetDeployments().SingleOrDefault(); if (deployment == null) { - await context.AdminFollowup("No deployment found."); + await context.Followup("No deployment found."); return; } try { var group = ci.WrapCodexContainers(deployment.CodexInstances.Select(i => i.Container).ToArray()); - await action(group); + await action(group, deployment.Metadata.Name); } catch (Exception ex) { - await context.AdminFollowup("Failed to wrap nodes with exception: " + ex); + await context.Followup("Failed to wrap nodes with exception: " + ex); } } } @@ -277,24 +286,31 @@ namespace BiblioTech.Commands protected override async Task onSubCommand(CommandContext context) { - await OnDeployment(context, async group => + await OnDeployment(context, async (group, name) => { - await context.AdminFollowup($"{group.Count()} Codex nodes."); + var nl = Environment.NewLine; + var content = new List + { + $"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes." + }; + foreach (var node in group) { try { var info = node.GetDebugInfo(); - var nl = Environment.NewLine; var json = JsonConvert.SerializeObject(info, Formatting.Indented); var jsonInsert = $"{nl}```{nl}{json}{nl}```{nl}"; - await context.AdminFollowup($"Node '{node.GetName()}' responded with {jsonInsert}"); + content.Add($"Node '{node.GetName()}' responded with {jsonInsert}"); } catch (Exception ex) { - await context.AdminFollowup($"Node '{node.GetName()}' failed to respond with exception: " + ex); + content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex); } } + + var filename = $"netinfo-{NoWhitespaces(name)}.log"; + await context.FollowupWithAttachement(filename, string.Join(nl, content.ToArray())); }); } } @@ -316,9 +332,9 @@ namespace BiblioTech.Commands var peerId = await peerIdOption.Parse(context); if (string.IsNullOrEmpty(peerId)) return; - await OnDeployment(context, async group => + await OnDeployment(context, async (group, name) => { - await context.AdminFollowup($"Calling debug/peer for '{peerId}' on {group.Count()} Codex nodes."); + await context.Followup($"Calling debug/peer for '{peerId}' on {group.Count()} Codex nodes."); foreach (var node in group) { try @@ -327,15 +343,20 @@ namespace BiblioTech.Commands var nl = Environment.NewLine; var json = JsonConvert.SerializeObject(info, Formatting.Indented); var jsonInsert = $"{nl}```{nl}{json}{nl}```{nl}"; - await context.AdminFollowup($"Node '{node.GetName()}' responded with {jsonInsert}"); + await context.Followup($"Node '{node.GetName()}' responded with {jsonInsert}"); } catch (Exception ex) { - await context.AdminFollowup($"Node '{node.GetName()}' failed to respond with exception: " + ex); + await context.Followup($"Node '{node.GetName()}' failed to respond with exception: " + ex); } } }); } } + + private static string NoWhitespaces(string s) + { + return s.Replace(" ", "-"); + } } } diff --git a/Tools/BiblioTech/Commands/GetBalanceCommand.cs b/Tools/BiblioTech/Commands/GetBalanceCommand.cs index e0703d6..05337ab 100644 --- a/Tools/BiblioTech/Commands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/Commands/GetBalanceCommand.cs @@ -5,7 +5,7 @@ using GethPlugin; namespace BiblioTech.Commands { - public class GetBalanceCommand : BaseNetCommand + public class GetBalanceCommand : BaseGethCommand { private readonly UserAssociateCommand userAssociateCommand; private readonly UserOption optionalUser = new UserOption( @@ -19,7 +19,7 @@ namespace BiblioTech.Commands } public override string Name => "balance"; - public override string StartingMessage => "Fetching balance..."; + public override string StartingMessage => RandomBusyMessage.Get(); public override string Description => "Shows Eth and TestToken balance of an eth address."; public override CommandOption[] Options => new[] { optionalUser }; diff --git a/Tools/BiblioTech/Commands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs index b6dde9d..ed87a1e 100644 --- a/Tools/BiblioTech/Commands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -5,7 +5,7 @@ using GethPlugin; namespace BiblioTech.Commands { - public class MintCommand : BaseNetCommand + public class MintCommand : BaseGethCommand { private readonly Ether defaultEthToSend = 10.Eth(); private readonly TestToken defaultTestTokensToMint = 1024.TestTokens(); @@ -21,7 +21,7 @@ namespace BiblioTech.Commands } public override string Name => "mint"; - public override string StartingMessage => "Minting some tokens..."; + public override string StartingMessage => RandomBusyMessage.Get(); public override string Description => "Mint some TestTokens and send some Eth to the user if their balance is low."; public override CommandOption[] Options => new[] { optionalUser }; diff --git a/Tools/BiblioTech/Commands/SprCommand.cs b/Tools/BiblioTech/Commands/SprCommand.cs new file mode 100644 index 0000000..6d52b3d --- /dev/null +++ b/Tools/BiblioTech/Commands/SprCommand.cs @@ -0,0 +1,61 @@ +using BiblioTech.Options; +using CodexPlugin; +using Core; + +namespace BiblioTech.Commands +{ + public class SprCommand : BaseCodexCommand + { + private readonly Random random = new Random(); + private readonly List sprCache = new List(); + private DateTime lastUpdate = DateTime.MinValue; + + public SprCommand(CoreInterface ci) : base(ci) + { + } + + public override string Name => "boot"; + public override string StartingMessage => RandomBusyMessage.Get(); + public override string Description => "Gets an SPR. (Signed peer record, used for bootstrapping.)"; + + protected override async Task OnInvoke(CommandContext context) + { + if (ShouldUpdate()) + { + return true; + } + + await ReplyWithRandomSpr(context); + return false; + } + + protected override async Task Execute(CommandContext context, ICodexNodeGroup codexGroup) + { + lastUpdate = DateTime.UtcNow; + sprCache.Clear(); + + var infos = codexGroup.Select(c => c.GetDebugInfo()).ToArray(); + sprCache.AddRange(infos.Select(i => i.spr)); + + await ReplyWithRandomSpr(context); + } + + private async Task ReplyWithRandomSpr(CommandContext context) + { + if (!sprCache.Any()) + { + await context.Followup("I'm sorry, no SPRs are available... :c"); + return; + } + + var i = random.Next(0, sprCache.Count); + var spr = sprCache[i]; + await context.Followup($"Your SPR: '{spr}'"); + } + + private bool ShouldUpdate() + { + return (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); + } + } +} diff --git a/Tools/BiblioTech/Commands/UserAssociateCommand.cs b/Tools/BiblioTech/Commands/UserAssociateCommand.cs index a6160ec..2ede721 100644 --- a/Tools/BiblioTech/Commands/UserAssociateCommand.cs +++ b/Tools/BiblioTech/Commands/UserAssociateCommand.cs @@ -10,7 +10,7 @@ namespace BiblioTech.Commands isRequired: false); public override string Name => "set"; - public override string StartingMessage => "hold on..."; + public override string StartingMessage => RandomBusyMessage.Get(); public override string Description => "Associates a Discord user with an Ethereum address."; public override CommandOption[] Options => new CommandOption[] { ethOption, optionalUser }; diff --git a/Tools/BiblioTech/DeploymentsFilesMonitor.cs b/Tools/BiblioTech/DeploymentsFilesMonitor.cs index c02ed11..9278574 100644 --- a/Tools/BiblioTech/DeploymentsFilesMonitor.cs +++ b/Tools/BiblioTech/DeploymentsFilesMonitor.cs @@ -6,14 +6,16 @@ namespace BiblioTech { public class DeploymentsFilesMonitor { - private DateTime lastUpdate = DateTime.MinValue; - private CodexDeployment[] deployments = Array.Empty(); + private readonly List deployments = new List(); + + public void Initialize() + { + LoadDeployments(); + } public CodexDeployment[] GetDeployments() { - if (ShouldUpdate()) UpdateDeployments(); - - return deployments; + return deployments.ToArray(); } public async Task DownloadDeployment(IAttachment file) @@ -30,7 +32,7 @@ namespace BiblioTech { var targetFile = Path.Combine(Program.Config.EndpointsPath, Guid.NewGuid().ToString().ToLowerInvariant() + ".json"); File.WriteAllText(targetFile, str); - deployments = Array.Empty(); + deployments.Add(deploy); return true; } } @@ -44,22 +46,21 @@ namespace BiblioTech if (!Directory.Exists(path)) return false; var files = Directory.GetFiles(path); - foreach ( var file in files) + foreach (var file in files) { var deploy = ProcessFile(file); if (deploy != null && deploy.Metadata.Name == deploymentName) { File.Delete(file); - deployments = Array.Empty(); + deployments.Remove(deploy); return true; } } return false; } - private void UpdateDeployments() + private void LoadDeployments() { - lastUpdate = DateTime.UtcNow; var path = Program.Config.EndpointsPath; if (!Directory.Exists(path)) { @@ -69,7 +70,7 @@ namespace BiblioTech } var files = Directory.GetFiles(path); - deployments = files.Select(ProcessFile).Where(d => d != null).Cast().ToArray(); + deployments.AddRange(files.Select(ProcessFile).Where(d => d != null).Cast()); } private CodexDeployment? ProcessFile(string filename) @@ -84,10 +85,5 @@ namespace BiblioTech return null; } } - - private bool ShouldUpdate() - { - return !deployments.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); - } } } diff --git a/Tools/BiblioTech/Options/CommandContext.cs b/Tools/BiblioTech/Options/CommandContext.cs index 186bf2f..01567ce 100644 --- a/Tools/BiblioTech/Options/CommandContext.cs +++ b/Tools/BiblioTech/Options/CommandContext.cs @@ -15,12 +15,19 @@ namespace BiblioTech.Options public async Task Followup(string message) { - await Command.FollowupAsync(message, ephemeral: true); + await Command.ModifyOriginalResponseAsync(m => + { + m.Content = message; + }); } - public async Task AdminFollowup(string message) + public async Task FollowupWithAttachement(string filename, string content) { - await Command.FollowupAsync(message); + using var fileStream = new MemoryStream(); + using var streamWriter = new StreamWriter(fileStream); + await streamWriter.WriteAsync(content); + + await Command.FollowupWithFileAsync(fileStream, filename); } } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index e4c5733..ecd9147 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -21,6 +21,8 @@ namespace BiblioTech var uniformArgs = new ArgsUniform(PrintHelp, args); Config = uniformArgs.Parse(); + DeploymentFilesMonitor.Initialize(); + EnsurePath(Config.DataPath); EnsurePath(Config.UserDataPath); EnsurePath(Config.EndpointsPath); @@ -50,6 +52,7 @@ namespace BiblioTech var handler = new CommandHandler(client, new GetBalanceCommand(ci, associateCommand), new MintCommand(ci, associateCommand), + new SprCommand(ci), associateCommand, new AdminCommand(ci) ); diff --git a/Tools/BiblioTech/RandomBusyMessage.cs b/Tools/BiblioTech/RandomBusyMessage.cs new file mode 100644 index 0000000..5582767 --- /dev/null +++ b/Tools/BiblioTech/RandomBusyMessage.cs @@ -0,0 +1,25 @@ +namespace BiblioTech +{ + public static class RandomBusyMessage + { + private static readonly Random random = new Random(); + private static readonly string[] messages = new[] + { + "Working on it...", + "Doing that...", + "Hang on...", + "Making it so...", + "Reversing the polarity...", + "Factoring the polynomial...", + "Analyzing the wavelengths...", + "Charging the flux-capacitor...", + "Jumping to hyperspace...", + "Computing the ultimate answer..." + }; + + public static string Get() + { + return messages[random.Next(messages.Length)]; + } + } +} diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index b1190d3..1a78ac4 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -46,7 +46,10 @@ namespace BiblioTech public string[] GetInteractionReport(IUser user) { - var result = new List(); + var result = new List + { + $"User report create on {DateTime.UtcNow.ToString("o")}" + }; lock (repoLock) {