From b3da42522fcb93c53381a21ad520e8ca1e402c6b Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 08:57:59 +0200 Subject: [PATCH 01/18] Sets up project --- Tools/BiblioTech/BiblioTech.csproj | 10 ++++++++++ Tools/BiblioTech/Program.cs | 8 ++++++++ cs-codex-dist-testing.sln | 7 +++++++ 3 files changed, 25 insertions(+) create mode 100644 Tools/BiblioTech/BiblioTech.csproj create mode 100644 Tools/BiblioTech/Program.cs diff --git a/Tools/BiblioTech/BiblioTech.csproj b/Tools/BiblioTech/BiblioTech.csproj new file mode 100644 index 0000000..f02677b --- /dev/null +++ b/Tools/BiblioTech/BiblioTech.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs new file mode 100644 index 0000000..240f9c7 --- /dev/null +++ b/Tools/BiblioTech/Program.cs @@ -0,0 +1,8 @@ +public class Program +{ + public static void Main(string[] args) + { + Console.WriteLine("Starting Codex Discord bot BiblioTech..."); + + } +} \ No newline at end of file diff --git a/cs-codex-dist-testing.sln b/cs-codex-dist-testing.sln index 7d3dda9..a3e3ac8 100644 --- a/cs-codex-dist-testing.sln +++ b/cs-codex-dist-testing.sln @@ -43,6 +43,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistTestCore", "Tests\DistT EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodexNetDeployer", "Tools\CodexNetDeployer\CodexNetDeployer.csproj", "{3417D508-E2F4-4974-8988-BB124046D9E2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BiblioTech", "Tools\BiblioTech\BiblioTech.csproj", "{078ABA6D-A04E-4F62-A44C-EA66F1B66548}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -113,6 +115,10 @@ Global {3417D508-E2F4-4974-8988-BB124046D9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {3417D508-E2F4-4974-8988-BB124046D9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {3417D508-E2F4-4974-8988-BB124046D9E2}.Release|Any CPU.Build.0 = Release|Any CPU + {078ABA6D-A04E-4F62-A44C-EA66F1B66548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {078ABA6D-A04E-4F62-A44C-EA66F1B66548}.Debug|Any CPU.Build.0 = Debug|Any CPU + {078ABA6D-A04E-4F62-A44C-EA66F1B66548}.Release|Any CPU.ActiveCfg = Release|Any CPU + {078ABA6D-A04E-4F62-A44C-EA66F1B66548}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,6 +140,7 @@ Global {562EC700-6984-4C9A-83BF-3BF4E3EB1A64} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {E849B7BA-FDCC-4CFF-998F-845ED2F1BF40} = {88C2A621-8A98-4D07-8625-7900FC8EF89E} {3417D508-E2F4-4974-8988-BB124046D9E2} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} + {078ABA6D-A04E-4F62-A44C-EA66F1B66548} = {7591C5B3-D86E-4AE4-8ED2-B272D17FE7E3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {237BF0AA-9EC4-4659-AD9A-65DEB974250C} From 7179c7046338a4d21873bb6f0bcfb5e2682553bd Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 09:10:04 +0200 Subject: [PATCH 02/18] Sets up an echo command --- Tools/BiblioTech/BiblioTech.csproj | 4 ++ Tools/BiblioTech/CommandHandler.cs | 62 ++++++++++++++++++++++++++++ Tools/BiblioTech/HelloWorldModule.cs | 14 +++++++ Tools/BiblioTech/Program.cs | 31 +++++++++++--- 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 Tools/BiblioTech/CommandHandler.cs create mode 100644 Tools/BiblioTech/HelloWorldModule.cs diff --git a/Tools/BiblioTech/BiblioTech.csproj b/Tools/BiblioTech/BiblioTech.csproj index f02677b..ab4ef7e 100644 --- a/Tools/BiblioTech/BiblioTech.csproj +++ b/Tools/BiblioTech/BiblioTech.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs new file mode 100644 index 0000000..ef8330a --- /dev/null +++ b/Tools/BiblioTech/CommandHandler.cs @@ -0,0 +1,62 @@ +using Discord.Commands; +using Discord.WebSocket; +using System.Reflection; + +namespace BiblioTech +{ + public class CommandHandler + { + private readonly DiscordSocketClient client; + private readonly CommandService commands; + + // Retrieve client and CommandService instance via ctor + public CommandHandler(DiscordSocketClient client, CommandService commands) + { + this.commands = commands; + this.client = client; + } + + public async Task InstallCommandsAsync() + { + // Hook the MessageReceived event into our command handler + client.MessageReceived += HandleCommandAsync; + + // Here we discover all of the command modules in the entry + // assembly and load them. Starting from Discord.NET 2.0, a + // service provider is required to be passed into the + // module registration method to inject the + // required dependencies. + // + // If you do not use Dependency Injection, pass null. + // See Dependency Injection guide for more information. + await commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), + services: null); + } + + private async Task HandleCommandAsync(SocketMessage messageParam) + { + // Don't process the command if it was a system message + var message = messageParam as SocketUserMessage; + if (message == null) return; + + // Create a number to track where the prefix ends and the command begins + int argPos = 0; + + // Determine if the message is a command based on the prefix and make sure no bots trigger commands + if (!(message.HasCharPrefix('!', ref argPos) || + message.HasMentionPrefix(client.CurrentUser, ref argPos)) || + message.Author.IsBot) + return; + + // Create a WebSocket-based command context based on the message + var context = new SocketCommandContext(client, message); + + // Execute the command with the command context we just + // created, along with the service provider for precondition checks. + await commands.ExecuteAsync( + context: context, + argPos: argPos, + services: null); + } + } +} diff --git a/Tools/BiblioTech/HelloWorldModule.cs b/Tools/BiblioTech/HelloWorldModule.cs new file mode 100644 index 0000000..f0e711b --- /dev/null +++ b/Tools/BiblioTech/HelloWorldModule.cs @@ -0,0 +1,14 @@ +using Discord.Commands; + +namespace BiblioTech +{ + public class HelloWorldModule : ModuleBase + { + [Command("say")] + [Summary("Echoes a message.")] + public Task SayAsync([Remainder][Summary("The text to echo")] string echo) + { + return ReplyAsync("I say: " + echo); + } + } +} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 240f9c7..455618a 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,8 +1,29 @@ -public class Program -{ - public static void Main(string[] args) - { - Console.WriteLine("Starting Codex Discord bot BiblioTech..."); +using Discord; +using Discord.WebSocket; +public class Program +{ + public static Task Main(string[] args) => new Program().MainAsync(); + + private DiscordSocketClient client; + + public async Task MainAsync() + { + client = new DiscordSocketClient(); + + client.Log += Log; + + // You can assign your bot token to a string, and pass that in to connect. + // This is, however, insecure, particularly if you plan to have your code hosted in a public repository. + var token = "token"; + + await client.LoginAsync(TokenType.Bot, token); + await client.StartAsync(); + await Task.Delay(-1); + } + private Task Log(LogMessage msg) + { + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; } } \ No newline at end of file From bcb05cd0c9258d3bbe470f015e0797e46616e683 Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 11:01:24 +0200 Subject: [PATCH 03/18] Dockerizes discord bot --- Tools/BiblioTech/BiblioTech.csproj | 1 + Tools/BiblioTech/Configuration.cs | 13 +++++ Tools/BiblioTech/Program.cs | 62 ++++++++++++++------- Tools/BiblioTech/build-docker.bat | 2 + Tools/BiblioTech/docker/Dockerfile | 6 ++ Tools/BiblioTech/docker/docker-compose.yaml | 6 ++ 6 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 Tools/BiblioTech/Configuration.cs create mode 100644 Tools/BiblioTech/build-docker.bat create mode 100644 Tools/BiblioTech/docker/Dockerfile create mode 100644 Tools/BiblioTech/docker/docker-compose.yaml diff --git a/Tools/BiblioTech/BiblioTech.csproj b/Tools/BiblioTech/BiblioTech.csproj index ab4ef7e..0fc6193 100644 --- a/Tools/BiblioTech/BiblioTech.csproj +++ b/Tools/BiblioTech/BiblioTech.csproj @@ -9,6 +9,7 @@ + diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs new file mode 100644 index 0000000..73b2fa8 --- /dev/null +++ b/Tools/BiblioTech/Configuration.cs @@ -0,0 +1,13 @@ +using ArgsUniform; + +namespace BiblioTech +{ + public class Configuration + { + [Uniform("token", "t", "TOKEN", true, "Discord Application Token")] + public string ApplicationToken { get; set; } = string.Empty; + + [Uniform("deploys", "d", "DEPLOYS", false, "Path where deployment JSONs are located.")] + public string DeploymentsPath { get; set; } = "deploys"; + } +} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 455618a..e4f2708 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,29 +1,49 @@ -using Discord; +using ArgsUniform; +using Discord; using Discord.WebSocket; -public class Program +namespace BiblioTech { - public static Task Main(string[] args) => new Program().MainAsync(); - - private DiscordSocketClient client; - - public async Task MainAsync() + public class Program { - client = new DiscordSocketClient(); + private DiscordSocketClient client = null!; - client.Log += Log; + public static Configuration Config { get; private set; } = null!; - // You can assign your bot token to a string, and pass that in to connect. - // This is, however, insecure, particularly if you plan to have your code hosted in a public repository. - var token = "token"; + public static Task Main(string[] args) + { + var uniformArgs = new ArgsUniform(PrintHelp, args); + Config = uniformArgs.Parse(true); - await client.LoginAsync(TokenType.Bot, token); - await client.StartAsync(); - await Task.Delay(-1); + return new Program().MainAsync(); + } + + public async Task MainAsync() + { + Console.WriteLine("Starting Codex Discord Bot..."); + client = new DiscordSocketClient(); + + client.Log += Log; + + // You can assign your bot token to a string, and pass that in to connect. + // This is, however, insecure, particularly if you plan to have your code hosted in a public repository. + var token = "token"; + + await client.LoginAsync(TokenType.Bot, token); + await client.StartAsync(); + Console.WriteLine("Running..."); + await Task.Delay(-1); + } + + private static void PrintHelp() + { + Console.WriteLine("BiblioTech - Codex Discord Bot"); + } + + private Task Log(LogMessage msg) + { + Console.WriteLine(msg.ToString()); + return Task.CompletedTask; + } } - private Task Log(LogMessage msg) - { - Console.WriteLine(msg.ToString()); - return Task.CompletedTask; - } -} \ No newline at end of file +} diff --git a/Tools/BiblioTech/build-docker.bat b/Tools/BiblioTech/build-docker.bat new file mode 100644 index 0000000..bbcc64a --- /dev/null +++ b/Tools/BiblioTech/build-docker.bat @@ -0,0 +1,2 @@ +docker build -f docker/Dockerfile -t thatbenbierens/codex-discordbot:initial ../.. +docker push thatbenbierens/codex-discordbot:initial diff --git a/Tools/BiblioTech/docker/Dockerfile b/Tools/BiblioTech/docker/Dockerfile new file mode 100644 index 0000000..7f3844b --- /dev/null +++ b/Tools/BiblioTech/docker/Dockerfile @@ -0,0 +1,6 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 + +WORKDIR app +COPY ./Tools/BiblioTech ./Tools/BiblioTech +COPY ./Framework/ArgsUniform ./Framework/ArgsUniform +CMD ["dotnet", "run", "--project", "Tools/BiblioTech"] diff --git a/Tools/BiblioTech/docker/docker-compose.yaml b/Tools/BiblioTech/docker/docker-compose.yaml new file mode 100644 index 0000000..f283ac7 --- /dev/null +++ b/Tools/BiblioTech/docker/docker-compose.yaml @@ -0,0 +1,6 @@ +services: + bibliotech-discordbot: + image: thatbenbierens/codex-discordbot:initial + environment: + - TOKEN=tokenplz + - DEPLOYS=deploypath From 8c7229504e2c4b0588894c2baef344f7b4e670b5 Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 11:21:06 +0200 Subject: [PATCH 04/18] do not print token --- Tools/BiblioTech/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index e4f2708..cd35718 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -13,7 +13,7 @@ namespace BiblioTech public static Task Main(string[] args) { var uniformArgs = new ArgsUniform(PrintHelp, args); - Config = uniformArgs.Parse(true); + Config = uniformArgs.Parse(); return new Program().MainAsync(); } From f33866efc1f90cb472c68a0234f5fe0076254d15 Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 13:48:15 +0200 Subject: [PATCH 05/18] setting up slash commands --- Tools/BiblioTech/BiblioTech.csproj | 1 + Tools/BiblioTech/CommandHandler.cs | 7 ++- Tools/BiblioTech/DeploymentFilesMonitor.cs | 47 +++++++++++++++++ Tools/BiblioTech/HelloWorldCommand.cs | 61 ++++++++++++++++++++++ Tools/BiblioTech/Program.cs | 16 ++++-- Tools/BiblioTech/docker/Dockerfile | 3 +- 6 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 Tools/BiblioTech/DeploymentFilesMonitor.cs create mode 100644 Tools/BiblioTech/HelloWorldCommand.cs diff --git a/Tools/BiblioTech/BiblioTech.csproj b/Tools/BiblioTech/BiblioTech.csproj index 0fc6193..9c32ad4 100644 --- a/Tools/BiblioTech/BiblioTech.csproj +++ b/Tools/BiblioTech/BiblioTech.csproj @@ -10,6 +10,7 @@ + diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index ef8330a..42290a5 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -43,10 +43,9 @@ namespace BiblioTech int argPos = 0; // Determine if the message is a command based on the prefix and make sure no bots trigger commands - if (!(message.HasCharPrefix('!', ref argPos) || - message.HasMentionPrefix(client.CurrentUser, ref argPos)) || - message.Author.IsBot) - return; + if (message.Author.IsBot) return; + if (!message.HasMentionPrefix(client.CurrentUser, ref argPos) && + !message.Content.StartsWith("!")) return; // Create a WebSocket-based command context based on the message var context = new SocketCommandContext(client, message); diff --git a/Tools/BiblioTech/DeploymentFilesMonitor.cs b/Tools/BiblioTech/DeploymentFilesMonitor.cs new file mode 100644 index 0000000..0d8f054 --- /dev/null +++ b/Tools/BiblioTech/DeploymentFilesMonitor.cs @@ -0,0 +1,47 @@ +using CodexPlugin; +using Newtonsoft.Json; + +namespace BiblioTech +{ + public class DeploymentFilesMonitor + { + private DateTime lastUpdate = DateTime.MinValue; + private CodexDeployment[] deployments = Array.Empty(); + + public CodexDeployment[] GetDeployments() + { + if (ShouldUpdate()) + { + UpdateDeployments(); + } + + return deployments; + } + + private void UpdateDeployments() + { + lastUpdate = DateTime.UtcNow; + var path = Program.Config.DeploymentsPath; + var files = Directory.GetFiles(path); + deployments = files.Select(ProcessFile).Where(d => d != null).Cast().ToArray(); + } + + private CodexDeployment? ProcessFile(string filename) + { + try + { + var lines = File.ReadAllLines(filename); + return JsonConvert.DeserializeObject(string.Join(" ", lines)); + } + catch + { + return null; + } + } + + private bool ShouldUpdate() + { + return !deployments.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); + } + } +} diff --git a/Tools/BiblioTech/HelloWorldCommand.cs b/Tools/BiblioTech/HelloWorldCommand.cs new file mode 100644 index 0000000..2a7ef6a --- /dev/null +++ b/Tools/BiblioTech/HelloWorldCommand.cs @@ -0,0 +1,61 @@ +using Discord; +using Discord.Net; +using Discord.WebSocket; +using Newtonsoft.Json; + +namespace BiblioTech +{ + public class HelloWorldCommand + { + private readonly DiscordSocketClient client; + + public HelloWorldCommand(DiscordSocketClient client) + { + this.client = client; + + client.Ready += Client_Ready; + } + + private async Task Client_Ready() + { + // Let's build a guild command! We're going to need a guild so lets just put that in a variable. + var guild = client.Guilds.Single(g => g.Name == "ThatBen's server"); + + // Next, lets create our slash command builder. This is like the embed builder but for slash commands. + var guildCommand = new SlashCommandBuilder(); + + // Note: Names have to be all lowercase and match the regular expression ^[\w-]{3,32}$ + guildCommand.WithName("do-thing"); + + // Descriptions can have a max length of 100. + guildCommand.WithDescription("This command does the thing!"); + + //// Let's do our global command + //var globalCommand = new SlashCommandBuilder(); + //globalCommand.WithName("first-global-command"); + //globalCommand.WithDescription("This is my first global slash command"); + + try + { + // Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command. + await guild.CreateApplicationCommandAsync(guildCommand.Build()); + + // With global commands we don't need the guild. + //await client.CreateGlobalApplicationCommandAsync(globalCommand.Build()); + // Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development. + // For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command. + } + catch (ApplicationCommandException exception) + { + // If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is. + var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); + + // You can send this error somewhere or just print it to the console, for this example we're just going to print it. + Console.WriteLine(json); + } + } + + + + } +} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index cd35718..4950f26 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,6 +1,9 @@ using ArgsUniform; using Discord; +using Discord.Commands; +using Discord.Net; using Discord.WebSocket; +using Newtonsoft.Json; namespace BiblioTech { @@ -9,6 +12,7 @@ namespace BiblioTech private DiscordSocketClient client = null!; public static Configuration Config { get; private set; } = null!; + public static DeploymentFilesMonitor DeploymentFilesMonitor { get; } = new DeploymentFilesMonitor(); public static Task Main(string[] args) { @@ -22,14 +26,16 @@ namespace BiblioTech { Console.WriteLine("Starting Codex Discord Bot..."); client = new DiscordSocketClient(); - client.Log += Log; - // You can assign your bot token to a string, and pass that in to connect. - // This is, however, insecure, particularly if you plan to have your code hosted in a public repository. - var token = "token"; + var helloWorld = new HelloWorldCommand(client); - await client.LoginAsync(TokenType.Bot, token); + //var cmdService = new CommandService(); + //var handler = new CommandHandler(client, cmdService); + //await handler.InstallCommandsAsync(); + //Console.WriteLine("Command handler installed..."); + + await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.StartAsync(); Console.WriteLine("Running..."); await Task.Delay(-1); diff --git a/Tools/BiblioTech/docker/Dockerfile b/Tools/BiblioTech/docker/Dockerfile index 7f3844b..7b7d6c7 100644 --- a/Tools/BiblioTech/docker/Dockerfile +++ b/Tools/BiblioTech/docker/Dockerfile @@ -2,5 +2,6 @@ FROM mcr.microsoft.com/dotnet/sdk:7.0 WORKDIR app COPY ./Tools/BiblioTech ./Tools/BiblioTech -COPY ./Framework/ArgsUniform ./Framework/ArgsUniform +COPY ./Framework ./Framework +COPY ./ProjectPlugins ./ProjectPlugins CMD ["dotnet", "run", "--project", "Tools/BiblioTech"] From 888b19d8e5dc24632401aceaa7be5ce4f47be899 Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 13:55:56 +0200 Subject: [PATCH 06/18] working example of slash commands with arguments --- Tools/BiblioTech/HelloWorldCommand.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Tools/BiblioTech/HelloWorldCommand.cs b/Tools/BiblioTech/HelloWorldCommand.cs index 2a7ef6a..ac58f2f 100644 --- a/Tools/BiblioTech/HelloWorldCommand.cs +++ b/Tools/BiblioTech/HelloWorldCommand.cs @@ -14,6 +14,14 @@ namespace BiblioTech this.client = client; client.Ready += Client_Ready; + client.SlashCommandExecuted += SlashCommandHandler; + } + + private async Task SlashCommandHandler(SocketSlashCommand command) + { + var cheeseOption = command.Data.Options.SingleOrDefault(o => o.Name == "cheese"); + var numberOption = command.Data.Options.SingleOrDefault(o => o.Name == "numberofthings"); + await command.RespondAsync($"Dear {command.User.Username}, You executed {command.Data.Name} with cheese: {cheeseOption.Value} and number: {numberOption.Value}"); } private async Task Client_Ready() @@ -22,13 +30,12 @@ namespace BiblioTech var guild = client.Guilds.Single(g => g.Name == "ThatBen's server"); // Next, lets create our slash command builder. This is like the embed builder but for slash commands. - var guildCommand = new SlashCommandBuilder(); - - // Note: Names have to be all lowercase and match the regular expression ^[\w-]{3,32}$ - guildCommand.WithName("do-thing"); - - // Descriptions can have a max length of 100. - guildCommand.WithDescription("This command does the thing!"); + var guildCommand = new SlashCommandBuilder() + .WithName("do-thing") + .WithDescription("This command does the thing!") + .AddOption("cheese", ApplicationCommandOptionType.Boolean, "whether you like cheese", isRequired: true) + .AddOption("numberofthings", ApplicationCommandOptionType.Number, "count them please", isRequired: true) + ; //// Let's do our global command //var globalCommand = new SlashCommandBuilder(); From 766e2f5c20f7f22c2c42d3d9e6698863571d421d Mon Sep 17 00:00:00 2001 From: benbierens Date: Wed, 18 Oct 2023 14:59:39 +0200 Subject: [PATCH 07/18] Very basic endpoint pinging that might not even work. --- Tools/BiblioTech/CodexEndpoints.cs | 10 +++ Tools/BiblioTech/CommandHandler.cs | 61 ---------------- Tools/BiblioTech/Configuration.cs | 7 +- Tools/BiblioTech/DeploymentFilesMonitor.cs | 47 ------------ Tools/BiblioTech/EndpointsFilesMonitor.cs | 78 ++++++++++++++++++++ Tools/BiblioTech/EndpointsMonitor.cs | 81 +++++++++++++++++++++ Tools/BiblioTech/HelloWorldCommand.cs | 3 - Tools/BiblioTech/HelloWorldModule.cs | 14 ---- Tools/BiblioTech/Program.cs | 23 +++--- Tools/BiblioTech/StatusCommand.cs | 50 +++++++++++++ Tools/BiblioTech/docker/docker-compose.yaml | 2 +- 11 files changed, 239 insertions(+), 137 deletions(-) create mode 100644 Tools/BiblioTech/CodexEndpoints.cs delete mode 100644 Tools/BiblioTech/CommandHandler.cs delete mode 100644 Tools/BiblioTech/DeploymentFilesMonitor.cs create mode 100644 Tools/BiblioTech/EndpointsFilesMonitor.cs create mode 100644 Tools/BiblioTech/EndpointsMonitor.cs delete mode 100644 Tools/BiblioTech/HelloWorldModule.cs create mode 100644 Tools/BiblioTech/StatusCommand.cs diff --git a/Tools/BiblioTech/CodexEndpoints.cs b/Tools/BiblioTech/CodexEndpoints.cs new file mode 100644 index 0000000..706c27b --- /dev/null +++ b/Tools/BiblioTech/CodexEndpoints.cs @@ -0,0 +1,10 @@ +using Utils; + +namespace BiblioTech +{ + public class CodexEndpoints + { + public string Name { get; set; } = string.Empty; + public Address[] Addresses { get; set; } = Array.Empty
(); + } +} diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs deleted file mode 100644 index 42290a5..0000000 --- a/Tools/BiblioTech/CommandHandler.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Discord.Commands; -using Discord.WebSocket; -using System.Reflection; - -namespace BiblioTech -{ - public class CommandHandler - { - private readonly DiscordSocketClient client; - private readonly CommandService commands; - - // Retrieve client and CommandService instance via ctor - public CommandHandler(DiscordSocketClient client, CommandService commands) - { - this.commands = commands; - this.client = client; - } - - public async Task InstallCommandsAsync() - { - // Hook the MessageReceived event into our command handler - client.MessageReceived += HandleCommandAsync; - - // Here we discover all of the command modules in the entry - // assembly and load them. Starting from Discord.NET 2.0, a - // service provider is required to be passed into the - // module registration method to inject the - // required dependencies. - // - // If you do not use Dependency Injection, pass null. - // See Dependency Injection guide for more information. - await commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), - services: null); - } - - private async Task HandleCommandAsync(SocketMessage messageParam) - { - // Don't process the command if it was a system message - var message = messageParam as SocketUserMessage; - if (message == null) return; - - // Create a number to track where the prefix ends and the command begins - int argPos = 0; - - // Determine if the message is a command based on the prefix and make sure no bots trigger commands - if (message.Author.IsBot) return; - if (!message.HasMentionPrefix(client.CurrentUser, ref argPos) && - !message.Content.StartsWith("!")) return; - - // Create a WebSocket-based command context based on the message - var context = new SocketCommandContext(client, message); - - // Execute the command with the command context we just - // created, along with the service provider for precondition checks. - await commands.ExecuteAsync( - context: context, - argPos: argPos, - services: null); - } - } -} diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 73b2fa8..94a8ec3 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -7,7 +7,10 @@ namespace BiblioTech [Uniform("token", "t", "TOKEN", true, "Discord Application Token")] public string ApplicationToken { get; set; } = string.Empty; - [Uniform("deploys", "d", "DEPLOYS", false, "Path where deployment JSONs are located.")] - public string DeploymentsPath { get; set; } = "deploys"; + [Uniform("server-name", "sn", "SERVERNAME", true, "Name of the Discord server")] + public string ServerName { get; set; } = string.Empty; + + [Uniform("endpoints", "e", "ENDPOINTS", false, "Path where endpoint JSONs are located. Also accepts codex-deployment JSONs.")] + public string EndpointsPath { get; set; } = "endpoints"; } } diff --git a/Tools/BiblioTech/DeploymentFilesMonitor.cs b/Tools/BiblioTech/DeploymentFilesMonitor.cs deleted file mode 100644 index 0d8f054..0000000 --- a/Tools/BiblioTech/DeploymentFilesMonitor.cs +++ /dev/null @@ -1,47 +0,0 @@ -using CodexPlugin; -using Newtonsoft.Json; - -namespace BiblioTech -{ - public class DeploymentFilesMonitor - { - private DateTime lastUpdate = DateTime.MinValue; - private CodexDeployment[] deployments = Array.Empty(); - - public CodexDeployment[] GetDeployments() - { - if (ShouldUpdate()) - { - UpdateDeployments(); - } - - return deployments; - } - - private void UpdateDeployments() - { - lastUpdate = DateTime.UtcNow; - var path = Program.Config.DeploymentsPath; - var files = Directory.GetFiles(path); - deployments = files.Select(ProcessFile).Where(d => d != null).Cast().ToArray(); - } - - private CodexDeployment? ProcessFile(string filename) - { - try - { - var lines = File.ReadAllLines(filename); - return JsonConvert.DeserializeObject(string.Join(" ", lines)); - } - catch - { - return null; - } - } - - private bool ShouldUpdate() - { - return !deployments.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); - } - } -} diff --git a/Tools/BiblioTech/EndpointsFilesMonitor.cs b/Tools/BiblioTech/EndpointsFilesMonitor.cs new file mode 100644 index 0000000..9136740 --- /dev/null +++ b/Tools/BiblioTech/EndpointsFilesMonitor.cs @@ -0,0 +1,78 @@ +using CodexPlugin; +using KubernetesWorkflow; +using Newtonsoft.Json; +using Utils; + +namespace BiblioTech +{ + public class EndpointsFilesMonitor + { + private DateTime lastUpdate = DateTime.MinValue; + private CodexEndpoints[] endpoints = Array.Empty(); + + public CodexEndpoints[] GetEndpoints() + { + if (ShouldUpdate()) + { + UpdateEndpoints(); + } + + return endpoints; + } + + private void UpdateEndpoints() + { + lastUpdate = DateTime.UtcNow; + var path = Program.Config.EndpointsPath; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + File.WriteAllText(Path.Combine(path, "readme.txt"), "Place codex-deployment.json or codex-endpoints.json here."); + return; + } + + var files = Directory.GetFiles(path); + endpoints = files.Select(ProcessFile).Where(d => d != null).Cast().ToArray(); + } + + private CodexEndpoints? ProcessFile(string filename) + { + try + { + var lines = string.Join(" ", File.ReadAllLines(filename)); + try + { + return JsonConvert.DeserializeObject(lines); + } + catch { } + + return ConvertToEndpoints(JsonConvert.DeserializeObject(lines)); + } + catch + { + return null; + } + } + + private CodexEndpoints? ConvertToEndpoints(CodexDeployment? codexDeployment) + { + if (codexDeployment == null) return null; + + return new CodexEndpoints + { + Name = "codex-deployment-" + codexDeployment.Metadata.StartUtc.ToString("o"), + Addresses = codexDeployment.CodexContainers.Select(ConvertToAddress).ToArray() + }; + } + + private Address ConvertToAddress(RunningContainer rc) + { + return rc.ClusterExternalAddress; + } + + private bool ShouldUpdate() + { + return !endpoints.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); + } + } +} diff --git a/Tools/BiblioTech/EndpointsMonitor.cs b/Tools/BiblioTech/EndpointsMonitor.cs new file mode 100644 index 0000000..27e61bd --- /dev/null +++ b/Tools/BiblioTech/EndpointsMonitor.cs @@ -0,0 +1,81 @@ +using CodexPlugin; +using Core; +using KubernetesWorkflow; + +namespace BiblioTech +{ + public class EndpointsMonitor + { + private readonly EndpointsFilesMonitor fileMonitor; + private readonly EntryPoint entryPoint; + private DateTime lastUpdate = DateTime.MinValue; + private string report = string.Empty; + + public EndpointsMonitor(EndpointsFilesMonitor fileMonitor, EntryPoint entryPoint) + { + this.fileMonitor = fileMonitor; + this.entryPoint = entryPoint; + } + + public async Task GetReport() + { + if (ShouldUpdate()) + { + await UpdateReport(); + } + + return report; + } + + private async Task UpdateReport() + { + lastUpdate = DateTime.UtcNow; + + var endpoints = fileMonitor.GetEndpoints(); + if (!endpoints.Any()) + { + report = "There are no networks currently online."; + } + else + { + var nl = Environment.NewLine; + report = $"There are {endpoints.Length} networks online." + nl; + for (var i = 0; i < endpoints.Length; i++) + { + var e = endpoints[i]; + report += $" [{i} - {e.Name}] = {await GetStatusMessage(e)}"; + } + } + } + + private async Task GetStatusMessage(CodexEndpoints e) + { + var success = 0; + foreach (var addr in e.Addresses) + { + await Task.Run(() => + { + // this is definitely not going to work: + var rc = new RunningContainer(null!, null!, null!, "", addr, addr); + + var access = new CodexAccess(entryPoint.Tools, rc, null!); + var debugInfo = access.GetDebugInfo(); + + if (!string.IsNullOrEmpty(debugInfo.id)) + { + success++; + } + }); + } + + return $"{success} / {e.Addresses.Length} online."; + // todo: analyze returned peerIDs to say something about + // the number of none-test-net managed nodes seen on the network. + } + + private bool ShouldUpdate() + { + return string.IsNullOrEmpty(report) || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); + } + } +} diff --git a/Tools/BiblioTech/HelloWorldCommand.cs b/Tools/BiblioTech/HelloWorldCommand.cs index ac58f2f..8659acf 100644 --- a/Tools/BiblioTech/HelloWorldCommand.cs +++ b/Tools/BiblioTech/HelloWorldCommand.cs @@ -61,8 +61,5 @@ namespace BiblioTech Console.WriteLine(json); } } - - - } } diff --git a/Tools/BiblioTech/HelloWorldModule.cs b/Tools/BiblioTech/HelloWorldModule.cs deleted file mode 100644 index f0e711b..0000000 --- a/Tools/BiblioTech/HelloWorldModule.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Discord.Commands; - -namespace BiblioTech -{ - public class HelloWorldModule : ModuleBase - { - [Command("say")] - [Summary("Echoes a message.")] - public Task SayAsync([Remainder][Summary("The text to echo")] string echo) - { - return ReplyAsync("I say: " + echo); - } - } -} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 4950f26..e063f12 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,9 +1,8 @@ using ArgsUniform; +using Core; using Discord; -using Discord.Commands; -using Discord.Net; using Discord.WebSocket; -using Newtonsoft.Json; +using Logging; namespace BiblioTech { @@ -12,7 +11,7 @@ namespace BiblioTech private DiscordSocketClient client = null!; public static Configuration Config { get; private set; } = null!; - public static DeploymentFilesMonitor DeploymentFilesMonitor { get; } = new DeploymentFilesMonitor(); + public static EndpointsFilesMonitor DeploymentFilesMonitor { get; } = new EndpointsFilesMonitor(); public static Task Main(string[] args) { @@ -28,12 +27,18 @@ namespace BiblioTech client = new DiscordSocketClient(); client.Log += Log; - var helloWorld = new HelloWorldCommand(client); + ProjectPlugin.Load(); + var entryPoint = new EntryPoint(new ConsoleLog(), new KubernetesWorkflow.Configuration( + kubeConfigFile: null, // todo: readonly file + operationTimeout: TimeSpan.FromMinutes(5), + retryDelay: TimeSpan.FromSeconds(10), + kubernetesNamespace: "not-applicable"), "datafiles"); - //var cmdService = new CommandService(); - //var handler = new CommandHandler(client, cmdService); - //await handler.InstallCommandsAsync(); - //Console.WriteLine("Command handler installed..."); + var fileMonitor = new EndpointsFilesMonitor(); + var monitor = new EndpointsMonitor(fileMonitor, entryPoint); + + var statusCommand = new StatusCommand(client, monitor); + //var helloWorld = new HelloWorldCommand(client); Example for how to do arguments. await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.StartAsync(); diff --git a/Tools/BiblioTech/StatusCommand.cs b/Tools/BiblioTech/StatusCommand.cs new file mode 100644 index 0000000..9d081c0 --- /dev/null +++ b/Tools/BiblioTech/StatusCommand.cs @@ -0,0 +1,50 @@ +using Discord.Net; +using Discord.WebSocket; +using Discord; +using Newtonsoft.Json; +using Core; + +namespace BiblioTech +{ + public class StatusCommand + { + private const string CommandName = "status"; + private readonly DiscordSocketClient client; + private readonly EndpointsMonitor monitor; + + public StatusCommand(DiscordSocketClient client, EndpointsMonitor monitor) + { + this.client = client; + this.monitor = monitor; + + client.Ready += Client_Ready; + client.SlashCommandExecuted += SlashCommandHandler; + } + + private async Task SlashCommandHandler(SocketSlashCommand command) + { + if (command.CommandName != CommandName) return; + + await command.RespondAsync(await monitor.GetReport()); + } + + private async Task Client_Ready() + { + var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName); + + var guildCommand = new SlashCommandBuilder() + .WithName(CommandName) + .WithDescription("Display status of test net."); + + try + { + await guild.CreateApplicationCommandAsync(guildCommand.Build()); + } + catch (HttpException exception) + { + var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); + Console.WriteLine(json); + } + } + } +} diff --git a/Tools/BiblioTech/docker/docker-compose.yaml b/Tools/BiblioTech/docker/docker-compose.yaml index f283ac7..dae80e4 100644 --- a/Tools/BiblioTech/docker/docker-compose.yaml +++ b/Tools/BiblioTech/docker/docker-compose.yaml @@ -3,4 +3,4 @@ services: image: thatbenbierens/codex-discordbot:initial environment: - TOKEN=tokenplz - - DEPLOYS=deploypath + - SERVERNAME=ThatBen's server From 991927b95f8670b5759186f03f31d0bb0afc6891 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 20 Oct 2023 09:49:23 +0200 Subject: [PATCH 08/18] Setting up balance-getting command --- ProjectPlugins/CodexPlugin/CodexDeployment.cs | 7 +- Tools/BiblioTech/BaseCommand.cs | 40 +++++++++ Tools/BiblioTech/BaseNetCommand.cs | 34 ++++++++ Tools/BiblioTech/CodexEndpoints.cs | 10 --- Tools/BiblioTech/CommandHandler.cs | 62 ++++++++++++++ Tools/BiblioTech/DeploymentsFilesMonitor.cs | 54 +++++++++++++ Tools/BiblioTech/EndpointsFilesMonitor.cs | 78 ------------------ Tools/BiblioTech/EndpointsMonitor.cs | 81 ------------------- Tools/BiblioTech/HelloWorldCommand.cs | 65 --------------- Tools/BiblioTech/Program.cs | 13 +-- Tools/BiblioTech/StatusCommand.cs | 50 ------------ .../TokenCommands/EthAddressOption.cs | 34 ++++++++ .../TokenCommands/GetBalanceCommand.cs | 41 ++++++++++ 13 files changed, 278 insertions(+), 291 deletions(-) create mode 100644 Tools/BiblioTech/BaseCommand.cs create mode 100644 Tools/BiblioTech/BaseNetCommand.cs delete mode 100644 Tools/BiblioTech/CodexEndpoints.cs create mode 100644 Tools/BiblioTech/CommandHandler.cs create mode 100644 Tools/BiblioTech/DeploymentsFilesMonitor.cs delete mode 100644 Tools/BiblioTech/EndpointsFilesMonitor.cs delete mode 100644 Tools/BiblioTech/EndpointsMonitor.cs delete mode 100644 Tools/BiblioTech/HelloWorldCommand.cs delete mode 100644 Tools/BiblioTech/StatusCommand.cs create mode 100644 Tools/BiblioTech/TokenCommands/EthAddressOption.cs create mode 100644 Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs diff --git a/ProjectPlugins/CodexPlugin/CodexDeployment.cs b/ProjectPlugins/CodexPlugin/CodexDeployment.cs index b44c6d2..f63d8bb 100644 --- a/ProjectPlugins/CodexPlugin/CodexDeployment.cs +++ b/ProjectPlugins/CodexPlugin/CodexDeployment.cs @@ -1,20 +1,23 @@ -using GethPlugin; +using CodexContractsPlugin; +using GethPlugin; using KubernetesWorkflow; namespace CodexPlugin { public class CodexDeployment { - public CodexDeployment(RunningContainer[] codexContainers, GethDeployment gethDeployment, RunningContainer? prometheusContainer, DeploymentMetadata metadata) + public CodexDeployment(RunningContainer[] codexContainers, GethDeployment gethDeployment, CodexContractsDeployment codexContractsDeployment, RunningContainer? prometheusContainer, DeploymentMetadata metadata) { CodexContainers = codexContainers; GethDeployment = gethDeployment; + CodexContractsDeployment = codexContractsDeployment; PrometheusContainer = prometheusContainer; Metadata = metadata; } public RunningContainer[] CodexContainers { get; } public GethDeployment GethDeployment { get; } + public CodexContractsDeployment CodexContractsDeployment { get; } public RunningContainer? PrometheusContainer { get; } public DeploymentMetadata Metadata { get; } } diff --git a/Tools/BiblioTech/BaseCommand.cs b/Tools/BiblioTech/BaseCommand.cs new file mode 100644 index 0000000..93ca84b --- /dev/null +++ b/Tools/BiblioTech/BaseCommand.cs @@ -0,0 +1,40 @@ +using Discord.WebSocket; +using Discord; + +namespace BiblioTech +{ + public abstract class BaseCommand + { + public abstract string Name { get; } + public abstract string Description { get; } + public virtual CommandOption[] Options + { + get + { + return Array.Empty(); + } + } + + public async Task SlashCommandHandler(SocketSlashCommand command) + { + if (command.CommandName != Name) return; + await Invoke(command); + } + + protected abstract Task Invoke(SocketSlashCommand command); + } + + public class CommandOption + { + public CommandOption(string name, string description, ApplicationCommandOptionType type) + { + Name = name; + Description = description; + Type = type; + } + + public string Name { get; } + public string Description { get; } + public ApplicationCommandOptionType Type { get; } + } +} diff --git a/Tools/BiblioTech/BaseNetCommand.cs b/Tools/BiblioTech/BaseNetCommand.cs new file mode 100644 index 0000000..73f0d09 --- /dev/null +++ b/Tools/BiblioTech/BaseNetCommand.cs @@ -0,0 +1,34 @@ +using CodexPlugin; +using Discord.WebSocket; + +namespace BiblioTech +{ + public abstract class BaseNetCommand : BaseCommand + { + private readonly DeploymentsFilesMonitor monitor; + + public BaseNetCommand(DeploymentsFilesMonitor monitor) + { + this.monitor = monitor; + } + + protected override async Task Invoke(SocketSlashCommand command) + { + var deployments = monitor.GetDeployments(); + if (deployments.Length == 0) + { + await command.RespondAsync("No deployments are currently available."); + return; + } + if (deployments.Length > 1) + { + await command.RespondAsync("Multiple deployments are online. I don't know which one to pick!"); + return; + } + + await Execute(command, deployments.Single()); + } + + protected abstract Task Execute(SocketSlashCommand command, CodexDeployment codexDeployment); + } +} diff --git a/Tools/BiblioTech/CodexEndpoints.cs b/Tools/BiblioTech/CodexEndpoints.cs deleted file mode 100644 index 706c27b..0000000 --- a/Tools/BiblioTech/CodexEndpoints.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Utils; - -namespace BiblioTech -{ - public class CodexEndpoints - { - public string Name { get; set; } = string.Empty; - public Address[] Addresses { get; set; } = Array.Empty
(); - } -} diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs new file mode 100644 index 0000000..963de2a --- /dev/null +++ b/Tools/BiblioTech/CommandHandler.cs @@ -0,0 +1,62 @@ +using Discord.Net; +using Discord.WebSocket; +using Discord; +using Newtonsoft.Json; + +namespace BiblioTech +{ + public class CommandHandler + { + private readonly DiscordSocketClient client; + private readonly BaseCommand[] commands; + + public CommandHandler(DiscordSocketClient client, params BaseCommand[] commands) + { + this.client = client; + this.commands = commands; + + client.Ready += Client_Ready; + client.SlashCommandExecuted += SlashCommandHandler; + } + + private async Task Client_Ready() + { + var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName); + + var builders = commands.Select(c => + { + var builder = new SlashCommandBuilder() + .WithName(c.Name) + .WithDescription(c.Description); + + foreach (var option in c.Options) + { + builder.AddOption(option.Name, option.Type, option.Description, isRequired: true); + } + + return builder; + }); + + try + { + foreach (var builder in builders) + { + await guild.CreateApplicationCommandAsync(builder.Build()); + } + } + catch (HttpException exception) + { + var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); + Console.WriteLine(json); + } + } + + private async Task SlashCommandHandler(SocketSlashCommand command) + { + foreach (var cmd in commands) + { + await cmd.SlashCommandHandler(command); + } + } + } +} diff --git a/Tools/BiblioTech/DeploymentsFilesMonitor.cs b/Tools/BiblioTech/DeploymentsFilesMonitor.cs new file mode 100644 index 0000000..ac44e58 --- /dev/null +++ b/Tools/BiblioTech/DeploymentsFilesMonitor.cs @@ -0,0 +1,54 @@ +using CodexPlugin; +using Newtonsoft.Json; + +namespace BiblioTech +{ + public class DeploymentsFilesMonitor + { + private DateTime lastUpdate = DateTime.MinValue; + private CodexDeployment[] deployments = Array.Empty(); + + public CodexDeployment[] GetDeployments() + { + if (ShouldUpdate()) + { + UpdateDeployments(); + } + + return deployments; + } + + private void UpdateDeployments() + { + lastUpdate = DateTime.UtcNow; + var path = Program.Config.EndpointsPath; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + File.WriteAllText(Path.Combine(path, "readme.txt"), "Place codex-deployment.json here."); + return; + } + + var files = Directory.GetFiles(path); + deployments = files.Select(ProcessFile).Where(d => d != null).Cast().ToArray(); + } + + private CodexDeployment? ProcessFile(string filename) + { + try + { + var lines = string.Join(" ", File.ReadAllLines(filename)); + return JsonConvert.DeserializeObject(lines); + } + catch + { + return null; + } + } + + private bool ShouldUpdate() + { + return !deployments.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); + } + } +} diff --git a/Tools/BiblioTech/EndpointsFilesMonitor.cs b/Tools/BiblioTech/EndpointsFilesMonitor.cs deleted file mode 100644 index 9136740..0000000 --- a/Tools/BiblioTech/EndpointsFilesMonitor.cs +++ /dev/null @@ -1,78 +0,0 @@ -using CodexPlugin; -using KubernetesWorkflow; -using Newtonsoft.Json; -using Utils; - -namespace BiblioTech -{ - public class EndpointsFilesMonitor - { - private DateTime lastUpdate = DateTime.MinValue; - private CodexEndpoints[] endpoints = Array.Empty(); - - public CodexEndpoints[] GetEndpoints() - { - if (ShouldUpdate()) - { - UpdateEndpoints(); - } - - return endpoints; - } - - private void UpdateEndpoints() - { - lastUpdate = DateTime.UtcNow; - var path = Program.Config.EndpointsPath; - if (!Directory.Exists(path)) - { - Directory.CreateDirectory(path); - File.WriteAllText(Path.Combine(path, "readme.txt"), "Place codex-deployment.json or codex-endpoints.json here."); - return; - } - - var files = Directory.GetFiles(path); - endpoints = files.Select(ProcessFile).Where(d => d != null).Cast().ToArray(); - } - - private CodexEndpoints? ProcessFile(string filename) - { - try - { - var lines = string.Join(" ", File.ReadAllLines(filename)); - try - { - return JsonConvert.DeserializeObject(lines); - } - catch { } - - return ConvertToEndpoints(JsonConvert.DeserializeObject(lines)); - } - catch - { - return null; - } - } - - private CodexEndpoints? ConvertToEndpoints(CodexDeployment? codexDeployment) - { - if (codexDeployment == null) return null; - - return new CodexEndpoints - { - Name = "codex-deployment-" + codexDeployment.Metadata.StartUtc.ToString("o"), - Addresses = codexDeployment.CodexContainers.Select(ConvertToAddress).ToArray() - }; - } - - private Address ConvertToAddress(RunningContainer rc) - { - return rc.ClusterExternalAddress; - } - - private bool ShouldUpdate() - { - return !endpoints.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); - } - } -} diff --git a/Tools/BiblioTech/EndpointsMonitor.cs b/Tools/BiblioTech/EndpointsMonitor.cs deleted file mode 100644 index 27e61bd..0000000 --- a/Tools/BiblioTech/EndpointsMonitor.cs +++ /dev/null @@ -1,81 +0,0 @@ -using CodexPlugin; -using Core; -using KubernetesWorkflow; - -namespace BiblioTech -{ - public class EndpointsMonitor - { - private readonly EndpointsFilesMonitor fileMonitor; - private readonly EntryPoint entryPoint; - private DateTime lastUpdate = DateTime.MinValue; - private string report = string.Empty; - - public EndpointsMonitor(EndpointsFilesMonitor fileMonitor, EntryPoint entryPoint) - { - this.fileMonitor = fileMonitor; - this.entryPoint = entryPoint; - } - - public async Task GetReport() - { - if (ShouldUpdate()) - { - await UpdateReport(); - } - - return report; - } - - private async Task UpdateReport() - { - lastUpdate = DateTime.UtcNow; - - var endpoints = fileMonitor.GetEndpoints(); - if (!endpoints.Any()) - { - report = "There are no networks currently online."; - } - else - { - var nl = Environment.NewLine; - report = $"There are {endpoints.Length} networks online." + nl; - for (var i = 0; i < endpoints.Length; i++) - { - var e = endpoints[i]; - report += $" [{i} - {e.Name}] = {await GetStatusMessage(e)}"; - } - } - } - - private async Task GetStatusMessage(CodexEndpoints e) - { - var success = 0; - foreach (var addr in e.Addresses) - { - await Task.Run(() => - { - // this is definitely not going to work: - var rc = new RunningContainer(null!, null!, null!, "", addr, addr); - - var access = new CodexAccess(entryPoint.Tools, rc, null!); - var debugInfo = access.GetDebugInfo(); - - if (!string.IsNullOrEmpty(debugInfo.id)) - { - success++; - } - }); - } - - return $"{success} / {e.Addresses.Length} online."; - // todo: analyze returned peerIDs to say something about - // the number of none-test-net managed nodes seen on the network. - } - - private bool ShouldUpdate() - { - return string.IsNullOrEmpty(report) || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); - } - } -} diff --git a/Tools/BiblioTech/HelloWorldCommand.cs b/Tools/BiblioTech/HelloWorldCommand.cs deleted file mode 100644 index 8659acf..0000000 --- a/Tools/BiblioTech/HelloWorldCommand.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Discord; -using Discord.Net; -using Discord.WebSocket; -using Newtonsoft.Json; - -namespace BiblioTech -{ - public class HelloWorldCommand - { - private readonly DiscordSocketClient client; - - public HelloWorldCommand(DiscordSocketClient client) - { - this.client = client; - - client.Ready += Client_Ready; - client.SlashCommandExecuted += SlashCommandHandler; - } - - private async Task SlashCommandHandler(SocketSlashCommand command) - { - var cheeseOption = command.Data.Options.SingleOrDefault(o => o.Name == "cheese"); - var numberOption = command.Data.Options.SingleOrDefault(o => o.Name == "numberofthings"); - await command.RespondAsync($"Dear {command.User.Username}, You executed {command.Data.Name} with cheese: {cheeseOption.Value} and number: {numberOption.Value}"); - } - - private async Task Client_Ready() - { - // Let's build a guild command! We're going to need a guild so lets just put that in a variable. - var guild = client.Guilds.Single(g => g.Name == "ThatBen's server"); - - // Next, lets create our slash command builder. This is like the embed builder but for slash commands. - var guildCommand = new SlashCommandBuilder() - .WithName("do-thing") - .WithDescription("This command does the thing!") - .AddOption("cheese", ApplicationCommandOptionType.Boolean, "whether you like cheese", isRequired: true) - .AddOption("numberofthings", ApplicationCommandOptionType.Number, "count them please", isRequired: true) - ; - - //// Let's do our global command - //var globalCommand = new SlashCommandBuilder(); - //globalCommand.WithName("first-global-command"); - //globalCommand.WithDescription("This is my first global slash command"); - - try - { - // Now that we have our builder, we can call the CreateApplicationCommandAsync method to make our slash command. - await guild.CreateApplicationCommandAsync(guildCommand.Build()); - - // With global commands we don't need the guild. - //await client.CreateGlobalApplicationCommandAsync(globalCommand.Build()); - // Using the ready event is a simple implementation for the sake of the example. Suitable for testing and development. - // For a production bot, it is recommended to only run the CreateGlobalApplicationCommandAsync() once for each command. - } - catch (ApplicationCommandException exception) - { - // If our command was invalid, we should catch an ApplicationCommandException. This exception contains the path of the error as well as the error message. You can serialize the Error field in the exception to get a visual of where your error is. - var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); - - // You can send this error somewhere or just print it to the console, for this example we're just going to print it. - Console.WriteLine(json); - } - } - } -} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index e063f12..fc4e693 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,4 +1,5 @@ using ArgsUniform; +using BiblioTech.TokenCommands; using Core; using Discord; using Discord.WebSocket; @@ -11,7 +12,7 @@ namespace BiblioTech private DiscordSocketClient client = null!; public static Configuration Config { get; private set; } = null!; - public static EndpointsFilesMonitor DeploymentFilesMonitor { get; } = new EndpointsFilesMonitor(); + public static DeploymentsFilesMonitor DeploymentFilesMonitor { get; } = new DeploymentsFilesMonitor(); public static Task Main(string[] args) { @@ -28,17 +29,19 @@ namespace BiblioTech client.Log += Log; ProjectPlugin.Load(); + ProjectPlugin.Load(); + ProjectPlugin.Load(); + var entryPoint = new EntryPoint(new ConsoleLog(), new KubernetesWorkflow.Configuration( kubeConfigFile: null, // todo: readonly file operationTimeout: TimeSpan.FromMinutes(5), retryDelay: TimeSpan.FromSeconds(10), kubernetesNamespace: "not-applicable"), "datafiles"); - var fileMonitor = new EndpointsFilesMonitor(); - var monitor = new EndpointsMonitor(fileMonitor, entryPoint); + var monitor = new DeploymentsFilesMonitor(); - var statusCommand = new StatusCommand(client, monitor); - //var helloWorld = new HelloWorldCommand(client); Example for how to do arguments. + var handler = new CommandHandler(client, + new GetBalanceCommand(monitor)); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.StartAsync(); diff --git a/Tools/BiblioTech/StatusCommand.cs b/Tools/BiblioTech/StatusCommand.cs deleted file mode 100644 index 9d081c0..0000000 --- a/Tools/BiblioTech/StatusCommand.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Discord.Net; -using Discord.WebSocket; -using Discord; -using Newtonsoft.Json; -using Core; - -namespace BiblioTech -{ - public class StatusCommand - { - private const string CommandName = "status"; - private readonly DiscordSocketClient client; - private readonly EndpointsMonitor monitor; - - public StatusCommand(DiscordSocketClient client, EndpointsMonitor monitor) - { - this.client = client; - this.monitor = monitor; - - client.Ready += Client_Ready; - client.SlashCommandExecuted += SlashCommandHandler; - } - - private async Task SlashCommandHandler(SocketSlashCommand command) - { - if (command.CommandName != CommandName) return; - - await command.RespondAsync(await monitor.GetReport()); - } - - private async Task Client_Ready() - { - var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName); - - var guildCommand = new SlashCommandBuilder() - .WithName(CommandName) - .WithDescription("Display status of test net."); - - try - { - await guild.CreateApplicationCommandAsync(guildCommand.Build()); - } - catch (HttpException exception) - { - var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); - Console.WriteLine(json); - } - } - } -} diff --git a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs b/Tools/BiblioTech/TokenCommands/EthAddressOption.cs new file mode 100644 index 0000000..5df010b --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/EthAddressOption.cs @@ -0,0 +1,34 @@ +using Discord.WebSocket; +using GethPlugin; + +namespace BiblioTech.TokenCommands +{ + public class EthAddressOption : CommandOption + { + public EthAddressOption() + : base(name: "EthAddress", + description: "Ethereum address starting with '0x'.", + type: Discord.ApplicationCommandOptionType.String) + { + } + + public async Task Parse(SocketSlashCommand command) + { + var ethOptionData = command.Data.Options.SingleOrDefault(o => o.Name == Name); + if (ethOptionData == null) + { + await command.RespondAsync("EthAddress option not received."); + return null; + } + var ethAddressStr = ethOptionData.Value as string; + if (string.IsNullOrEmpty(ethAddressStr)) + { + // todo, validate that it is an eth address. + await command.RespondAsync("EthAddress is null or invalid."); + return null; + } + + return new EthAddress(ethAddressStr); + } + } +} diff --git a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs new file mode 100644 index 0000000..b1a2aaf --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs @@ -0,0 +1,41 @@ +using CodexContractsPlugin; +using CodexPlugin; +using Core; +using Discord.WebSocket; +using GethPlugin; + +namespace BiblioTech.TokenCommands +{ + public class GetBalanceCommand : BaseNetCommand + { + private readonly EthAddressOption ethOption = new EthAddressOption(); + private readonly CoreInterface ci; + + public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) + : base(monitor) + { + this.ci = ci; + } + + public override string Name => "balance"; + public override string Description => "Shows Eth and TestToken balance of an eth address."; + public override CommandOption[] Options => new[] { ethOption }; + + protected override async Task Execute(SocketSlashCommand command, CodexDeployment codexDeployment) + { + var addr = await ethOption.Parse(command); + if (addr == null) return; + + var gethDeployment = codexDeployment.GethDeployment; + var contractsDeployment = codexDeployment.CodexContractsDeployment; + + var gethNode = ci.WrapGethDeployment(gethDeployment); + var contracts = ci.WrapCodexContractsDeployment(contractsDeployment); + + var eth = gethNode.GetEthBalance(addr); + var testTokens = contracts.GetTestTokenBalance(gethNode, addr); + + await command.RespondAsync($"Address '{addr.Address}' has {eth} and {testTokens}."); + } + } +} From 2b10f2ec58781b1cf2edca3a68202818e1bbaed2 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 20 Oct 2023 10:14:56 +0200 Subject: [PATCH 09/18] Adds mint command --- Tools/BiblioTech/BaseNetCommand.cs | 19 ++++-- Tools/BiblioTech/Program.cs | 7 ++- .../TokenCommands/GetBalanceCommand.cs | 12 +--- Tools/BiblioTech/TokenCommands/MintCommand.cs | 59 +++++++++++++++++++ 4 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 Tools/BiblioTech/TokenCommands/MintCommand.cs diff --git a/Tools/BiblioTech/BaseNetCommand.cs b/Tools/BiblioTech/BaseNetCommand.cs index 73f0d09..3036d5b 100644 --- a/Tools/BiblioTech/BaseNetCommand.cs +++ b/Tools/BiblioTech/BaseNetCommand.cs @@ -1,15 +1,19 @@ -using CodexPlugin; +using CodexContractsPlugin; +using Core; using Discord.WebSocket; +using GethPlugin; namespace BiblioTech { public abstract class BaseNetCommand : BaseCommand { private readonly DeploymentsFilesMonitor monitor; + private readonly CoreInterface ci; - public BaseNetCommand(DeploymentsFilesMonitor monitor) + public BaseNetCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) { this.monitor = monitor; + this.ci = ci; } protected override async Task Invoke(SocketSlashCommand command) @@ -26,9 +30,16 @@ namespace BiblioTech return; } - await Execute(command, deployments.Single()); + var codexDeployment = deployments.Single(); + var gethDeployment = codexDeployment.GethDeployment; + var contractsDeployment = codexDeployment.CodexContractsDeployment; + + var gethNode = ci.WrapGethDeployment(gethDeployment); + var contracts = ci.WrapCodexContractsDeployment(contractsDeployment); + + await Execute(command, gethNode, contracts); } - protected abstract Task Execute(SocketSlashCommand command, CodexDeployment codexDeployment); + protected abstract Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts); } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index fc4e693..cb8de54 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -33,15 +33,18 @@ namespace BiblioTech ProjectPlugin.Load(); var entryPoint = new EntryPoint(new ConsoleLog(), new KubernetesWorkflow.Configuration( - kubeConfigFile: null, // todo: readonly file + kubeConfigFile: null, operationTimeout: TimeSpan.FromMinutes(5), retryDelay: TimeSpan.FromSeconds(10), kubernetesNamespace: "not-applicable"), "datafiles"); var monitor = new DeploymentsFilesMonitor(); + var ci = entryPoint.CreateInterface(); + var handler = new CommandHandler(client, - new GetBalanceCommand(monitor)); + new GetBalanceCommand(monitor, ci), + new MintCommand(monitor, ci)); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.StartAsync(); diff --git a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs index b1a2aaf..59bf4be 100644 --- a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs @@ -9,29 +9,21 @@ namespace BiblioTech.TokenCommands public class GetBalanceCommand : BaseNetCommand { private readonly EthAddressOption ethOption = new EthAddressOption(); - private readonly CoreInterface ci; public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) - : base(monitor) + : base(monitor, ci) { - this.ci = ci; } public override string Name => "balance"; public override string Description => "Shows Eth and TestToken balance of an eth address."; public override CommandOption[] Options => new[] { ethOption }; - protected override async Task Execute(SocketSlashCommand command, CodexDeployment codexDeployment) + protected override async Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts) { var addr = await ethOption.Parse(command); if (addr == null) return; - var gethDeployment = codexDeployment.GethDeployment; - var contractsDeployment = codexDeployment.CodexContractsDeployment; - - var gethNode = ci.WrapGethDeployment(gethDeployment); - var contracts = ci.WrapCodexContractsDeployment(contractsDeployment); - var eth = gethNode.GetEthBalance(addr); var testTokens = contracts.GetTestTokenBalance(gethNode, addr); diff --git a/Tools/BiblioTech/TokenCommands/MintCommand.cs b/Tools/BiblioTech/TokenCommands/MintCommand.cs new file mode 100644 index 0000000..9d2bf95 --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/MintCommand.cs @@ -0,0 +1,59 @@ +using CodexContractsPlugin; +using Core; +using Discord.WebSocket; +using GethPlugin; + +namespace BiblioTech.TokenCommands +{ + public class MintCommand : BaseNetCommand + { + private readonly string nl = Environment.NewLine; + private readonly Ether defaultEthToSend = 10.Eth(); + private readonly TestToken defaultTestTokensToMint = 1024.TestTokens(); + private readonly EthAddressOption ethOption = new EthAddressOption(); + + public MintCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) + : base(monitor, ci) + { + } + + public override string Name => "mint"; + public override string Description => "Mint some TestTokens and send some Eth to the address if its balance is low."; + public override CommandOption[] Options => new[] { ethOption }; + + protected override async Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts) + { + var addr = await ethOption.Parse(command); + if (addr == null) return; + + var report = + ProcessEth(gethNode, addr) + + ProcessTestTokens(gethNode, contracts, addr); + + await command.RespondAsync(report); + } + + private string ProcessTestTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr) + { + var testTokens = contracts.GetTestTokenBalance(gethNode, addr); + if (testTokens.Amount < 64m) + { + contracts.MintTestTokens(gethNode, addr, defaultTestTokensToMint); + return $"Minted {defaultTestTokensToMint}." + nl; + } + return "TestToken balance over threshold." + nl; + } + + private string ProcessEth(IGethNode gethNode, EthAddress addr) + { + var eth = gethNode.GetEthBalance(addr); + if (eth.Eth < 1.0m) + { + gethNode.SendEth(addr, defaultEthToSend); + return $"Sent {defaultEthToSend}." + nl; + } + + return "Eth balance over threshold." + nl; + } + } +} From 8910c7ff272f198a8f3dafe586bb6bf3fe2cbdea Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 20 Oct 2023 10:15:38 +0200 Subject: [PATCH 10/18] Stores contracts deployment as part of CodexDeployment json. --- ProjectPlugins/GethPlugin/EthTokenExtensions.cs | 2 +- Tools/CodexNetDeployer/Deployer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProjectPlugins/GethPlugin/EthTokenExtensions.cs b/ProjectPlugins/GethPlugin/EthTokenExtensions.cs index 950a7e1..f9a9d8a 100644 --- a/ProjectPlugins/GethPlugin/EthTokenExtensions.cs +++ b/ProjectPlugins/GethPlugin/EthTokenExtensions.cs @@ -28,7 +28,7 @@ public override string ToString() { - return $"{Wei} Wei"; + return $"{Eth} Eth"; } } diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index 796fca8..65f11ad 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -79,7 +79,7 @@ namespace CodexNetDeployer CheckContainerRestarts(startResults); var codexContainers = startResults.Select(s => s.CodexNode.Container).ToArray(); - return new CodexDeployment(codexContainers, gethDeployment, metricsService, CreateMetadata(startUtc)); + return new CodexDeployment(codexContainers, gethDeployment, contractsDeployment, metricsService, CreateMetadata(startUtc)); } private EntryPoint CreateEntryPoint(ILog log) From 869aeb925374ace755c0183f4107f47933264c13 Mon Sep 17 00:00:00 2001 From: benbierens Date: Fri, 20 Oct 2023 11:20:38 +0200 Subject: [PATCH 11/18] Deals with timeout for operations that may take a while. --- Tools/BiblioTech/BaseCommand.cs | 13 +++++++- Tools/BiblioTech/BaseNetCommand.cs | 4 +-- Tools/BiblioTech/DeploymentsCommand.cs | 31 +++++++++++++++++++ Tools/BiblioTech/Program.cs | 4 ++- .../TokenCommands/EthAddressOption.cs | 2 +- .../TokenCommands/GetBalanceCommand.cs | 1 + Tools/BiblioTech/TokenCommands/MintCommand.cs | 5 +-- 7 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 Tools/BiblioTech/DeploymentsCommand.cs diff --git a/Tools/BiblioTech/BaseCommand.cs b/Tools/BiblioTech/BaseCommand.cs index 93ca84b..d62ba44 100644 --- a/Tools/BiblioTech/BaseCommand.cs +++ b/Tools/BiblioTech/BaseCommand.cs @@ -6,6 +6,7 @@ namespace BiblioTech public abstract class BaseCommand { public abstract string Name { get; } + public abstract string StartingMessage { get; } public abstract string Description { get; } public virtual CommandOption[] Options { @@ -18,7 +19,17 @@ namespace BiblioTech public async Task SlashCommandHandler(SocketSlashCommand command) { if (command.CommandName != Name) return; - await Invoke(command); + + try + { + await command.RespondAsync(StartingMessage); + await Invoke(command); + } + catch (Exception ex) + { + await command.FollowupAsync("Something failed while trying to do that..."); + Console.WriteLine(ex); + } } protected abstract Task Invoke(SocketSlashCommand command); diff --git a/Tools/BiblioTech/BaseNetCommand.cs b/Tools/BiblioTech/BaseNetCommand.cs index 3036d5b..83976a7 100644 --- a/Tools/BiblioTech/BaseNetCommand.cs +++ b/Tools/BiblioTech/BaseNetCommand.cs @@ -21,12 +21,12 @@ namespace BiblioTech var deployments = monitor.GetDeployments(); if (deployments.Length == 0) { - await command.RespondAsync("No deployments are currently available."); + await command.FollowupAsync("No deployments are currently available."); return; } if (deployments.Length > 1) { - await command.RespondAsync("Multiple deployments are online. I don't know which one to pick!"); + await command.FollowupAsync("Multiple deployments are online. I don't know which one to pick!"); return; } diff --git a/Tools/BiblioTech/DeploymentsCommand.cs b/Tools/BiblioTech/DeploymentsCommand.cs new file mode 100644 index 0000000..414b0c4 --- /dev/null +++ b/Tools/BiblioTech/DeploymentsCommand.cs @@ -0,0 +1,31 @@ +using Discord.WebSocket; + +namespace BiblioTech +{ + public class DeploymentsCommand : BaseCommand + { + private readonly DeploymentsFilesMonitor monitor; + + public DeploymentsCommand(DeploymentsFilesMonitor monitor) + { + this.monitor = monitor; + } + + public override string Name => "deployments"; + public override string StartingMessage => "Fetching deployments information..."; + public override string Description => "Lists known deployments"; + + protected override async Task Invoke(SocketSlashCommand command) + { + var deployments = monitor.GetDeployments(); + + if (!deployments.Any()) + { + await command.FollowupAsync("No deployments available."); + return; + } + + await command.FollowupAsync($"Deployments: {string.Join(", ", deployments.Select(d => d.Metadata.StartUtc.ToString("o")))}"); + } + } +} diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index cb8de54..fbf7880 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -44,7 +44,9 @@ namespace BiblioTech var handler = new CommandHandler(client, new GetBalanceCommand(monitor, ci), - new MintCommand(monitor, ci)); + new MintCommand(monitor, ci), + new DeploymentsCommand(monitor) + ); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.StartAsync(); diff --git a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs b/Tools/BiblioTech/TokenCommands/EthAddressOption.cs index 5df010b..6c351ba 100644 --- a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs +++ b/Tools/BiblioTech/TokenCommands/EthAddressOption.cs @@ -6,7 +6,7 @@ namespace BiblioTech.TokenCommands public class EthAddressOption : CommandOption { public EthAddressOption() - : base(name: "EthAddress", + : base(name: "ethaddress", description: "Ethereum address starting with '0x'.", type: Discord.ApplicationCommandOptionType.String) { diff --git a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs index 59bf4be..cec306f 100644 --- a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs @@ -16,6 +16,7 @@ namespace BiblioTech.TokenCommands } public override string Name => "balance"; + public override string StartingMessage => "Fetching balance..."; public override string Description => "Shows Eth and TestToken balance of an eth address."; public override CommandOption[] Options => new[] { ethOption }; diff --git a/Tools/BiblioTech/TokenCommands/MintCommand.cs b/Tools/BiblioTech/TokenCommands/MintCommand.cs index 9d2bf95..c22c91a 100644 --- a/Tools/BiblioTech/TokenCommands/MintCommand.cs +++ b/Tools/BiblioTech/TokenCommands/MintCommand.cs @@ -18,6 +18,7 @@ namespace BiblioTech.TokenCommands } public override string Name => "mint"; + public override string StartingMessage => "Minting some tokens..."; public override string Description => "Mint some TestTokens and send some Eth to the address if its balance is low."; public override CommandOption[] Options => new[] { ethOption }; @@ -26,11 +27,11 @@ namespace BiblioTech.TokenCommands var addr = await ethOption.Parse(command); if (addr == null) return; - var report = + var report = ProcessEth(gethNode, addr) + ProcessTestTokens(gethNode, contracts, addr); - await command.RespondAsync(report); + await command.FollowupAsync(report); } private string ProcessTestTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr) From 8ad2dee67c5a9cbe6d1e6855eb91fdb0948157b1 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 22 Oct 2023 09:32:03 +0200 Subject: [PATCH 12/18] Adds user repo. --- Tools/BiblioTech/Configuration.cs | 3 + Tools/BiblioTech/Program.cs | 1 + .../TokenCommands/GetBalanceCommand.cs | 2 + Tools/BiblioTech/UserRepo.cs | 128 ++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 Tools/BiblioTech/UserRepo.cs diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 94a8ec3..00b7979 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -12,5 +12,8 @@ namespace BiblioTech [Uniform("endpoints", "e", "ENDPOINTS", false, "Path where endpoint JSONs are located. Also accepts codex-deployment JSONs.")] public string EndpointsPath { get; set; } = "endpoints"; + + [Uniform("userdata", "u", "USERDATA", false, "Path where user data files will be saved.")] + public string UserDataPath { get; set; } = "userdata"; } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index fbf7880..395111f 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -13,6 +13,7 @@ namespace BiblioTech public static Configuration Config { get; private set; } = null!; public static DeploymentsFilesMonitor DeploymentFilesMonitor { get; } = new DeploymentsFilesMonitor(); + public static UserRepo UserRepo { get; } = new UserRepo(); public static Task Main(string[] args) { diff --git a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs index cec306f..add4c4a 100644 --- a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs @@ -15,6 +15,8 @@ namespace BiblioTech.TokenCommands { } + // connect users to eth address! + public override string Name => "balance"; public override string StartingMessage => "Fetching balance..."; public override string Description => "Shows Eth and TestToken balance of an eth address."; diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs new file mode 100644 index 0000000..e4cfa2c --- /dev/null +++ b/Tools/BiblioTech/UserRepo.cs @@ -0,0 +1,128 @@ +using CodexContractsPlugin; +using GethPlugin; +using Newtonsoft.Json; + +namespace BiblioTech +{ + public class UserRepo + { + private readonly object repoLock = new object(); + + public void AssociateUserWithAddress(ulong discordId, EthAddress address) + { + lock (repoLock) + { + SetUserAddress(discordId, address); + } + } + + public void ClearUserAssociatedAddress(ulong discordId) + { + lock (repoLock) + { + SetUserAddress(discordId, null); + } + } + + public void AddMintEventForUser(ulong discordId, EthAddress usedAddress, Ether eth, TestToken tokens) + { + lock (repoLock) + { + var user = GetOrCreate(discordId); + user.MintEvents.Add(new UserMintEvent(DateTime.UtcNow, usedAddress, eth, tokens)); + SaveUser(user); + } + } + + public EthAddress? GetCurrentAddressForUser(ulong discordId) + { + lock (repoLock) + { + return GetOrCreate(discordId).CurrentAddress; + } + } + + private void SetUserAddress(ulong discordId, EthAddress? address) + { + var user = GetOrCreate(discordId); + user.CurrentAddress = address; + user.AssociateEvents.Add(new UserAssociateAddressEvent(DateTime.UtcNow, address)); + SaveUser(user); + } + + private User GetOrCreate(ulong discordId) + { + var filename = GetFilename(discordId); + if (!File.Exists(filename)) + { + return CreateAndSaveNewUser(discordId); + } + return JsonConvert.DeserializeObject(File.ReadAllText(filename))!; + } + + private User CreateAndSaveNewUser(ulong discordId) + { + var newUser = new User(discordId, DateTime.UtcNow, null, new List(), new List()); + SaveUser(newUser); + return newUser; + } + + private void SaveUser(User user) + { + var filename = GetFilename(user.DiscordId); + if (File.Exists(filename)) File.Delete(filename); + File.WriteAllText(filename, JsonConvert.SerializeObject(user)); + } + + private static string GetFilename(ulong discordId) + { + return Path.Combine(Program.Config.UserDataPath, discordId.ToString() + ".json"); + } + } + + public class User + { + public User(ulong discordId, DateTime createdUtc, EthAddress? currentAddress, List associateEvents, List mintEvents) + { + DiscordId = discordId; + CreatedUtc = createdUtc; + CurrentAddress = currentAddress; + AssociateEvents = associateEvents; + MintEvents = mintEvents; + } + + public ulong DiscordId { get; } + public DateTime CreatedUtc { get; } + public EthAddress? CurrentAddress { get; set; } + public List AssociateEvents { get; } + public List MintEvents { get; } + } + + public class UserAssociateAddressEvent + { + public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress) + { + Utc = utc; + NewAddress = newAddress; + } + + public DateTime Utc { get; } + public EthAddress? NewAddress { get; } + } + + public class UserMintEvent + { + public UserMintEvent(DateTime utc, EthAddress usedAddress, Ether ethReceived, TestToken testTokensMinted) + { + Utc = utc; + UsedAddress = usedAddress; + EthReceived = ethReceived; + TestTokensMinted = testTokensMinted; + } + + public DateTime Utc { get; } + public EthAddress UsedAddress { get; } + public Ether EthReceived { get; } + public TestToken TestTokensMinted { get; } + } +} From 4aa47314807377f09e267102e835b7121b20e8e4 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 22 Oct 2023 10:10:52 +0200 Subject: [PATCH 13/18] setting up all the commands --- ProjectPlugins/GethPlugin/EthAddress.cs | 5 +++ Tools/BiblioTech/BaseCommand.cs | 17 ++++++++- Tools/BiblioTech/CommandHandler.cs | 2 +- .../ClearUserAssociationCommand.cs | 34 +++++++++++++++++ .../TokenCommands/EthAddressOption.cs | 3 +- .../TokenCommands/GetBalanceCommand.cs | 24 +++++++----- Tools/BiblioTech/TokenCommands/MintCommand.cs | 11 ++++-- .../TokenCommands/ReportHistoryCommand.cs | 34 +++++++++++++++++ .../BiblioTech/TokenCommands/ShowIdCommand.cs | 16 ++++++++ .../TokenCommands/UserAssociateCommand.cs | 33 +++++++++++++++++ Tools/BiblioTech/TokenCommands/UserOption.cs | 22 +++++++++++ Tools/BiblioTech/UserRepo.cs | 37 +++++++++++++++++++ 12 files changed, 222 insertions(+), 16 deletions(-) create mode 100644 Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs create mode 100644 Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs create mode 100644 Tools/BiblioTech/TokenCommands/ShowIdCommand.cs create mode 100644 Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs create mode 100644 Tools/BiblioTech/TokenCommands/UserOption.cs diff --git a/ProjectPlugins/GethPlugin/EthAddress.cs b/ProjectPlugins/GethPlugin/EthAddress.cs index 54c8638..2954c48 100644 --- a/ProjectPlugins/GethPlugin/EthAddress.cs +++ b/ProjectPlugins/GethPlugin/EthAddress.cs @@ -13,5 +13,10 @@ } public string Address { get; } + + public override string ToString() + { + return Address; + } } } diff --git a/Tools/BiblioTech/BaseCommand.cs b/Tools/BiblioTech/BaseCommand.cs index d62ba44..6086d34 100644 --- a/Tools/BiblioTech/BaseCommand.cs +++ b/Tools/BiblioTech/BaseCommand.cs @@ -1,5 +1,6 @@ using Discord.WebSocket; using Discord; +using BiblioTech.TokenCommands; namespace BiblioTech { @@ -33,19 +34,33 @@ namespace BiblioTech } protected abstract Task Invoke(SocketSlashCommand command); + + protected bool IsSenderAdmin(SocketSlashCommand command) + { + + } + + protected ulong GetUserId(UserOption userOption, SocketSlashCommand command) + { + var targetUser = userOption.GetOptionUserId(command); + if (IsSenderAdmin(command) && targetUser != null) return targetUser.Value; + return command.User.Id; + } } public class CommandOption { - public CommandOption(string name, string description, ApplicationCommandOptionType type) + public CommandOption(string name, string description, ApplicationCommandOptionType type, bool isRequired) { Name = name; Description = description; Type = type; + IsRequired = isRequired; } public string Name { get; } public string Description { get; } public ApplicationCommandOptionType Type { get; } + public bool IsRequired { get; } } } diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 963de2a..4e5d8de 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -31,7 +31,7 @@ namespace BiblioTech foreach (var option in c.Options) { - builder.AddOption(option.Name, option.Type, option.Description, isRequired: true); + builder.AddOption(option.Name, option.Type, option.Description, isRequired: option.IsRequired); } return builder; diff --git a/Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs b/Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs new file mode 100644 index 0000000..10aa2b2 --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs @@ -0,0 +1,34 @@ +using Discord.WebSocket; + +namespace BiblioTech.TokenCommands +{ + public class ClearUserAssociationCommand : BaseCommand + { + private readonly UserOption user = new UserOption( + description: "User to clear Eth address for.", + isRequired: true); + + public override string Name => "clear"; + public override string StartingMessage => "Hold on..."; + public override string Description => "Admin only. Clears current Eth address for a user, allowing them to set a new one."; + + protected override async Task Invoke(SocketSlashCommand command) + { + if (!IsSenderAdmin(command)) + { + await command.FollowupAsync("You're not an admin."); + return; + } + + var userId = user.GetOptionUserId(command); + if (userId == null) + { + await command.FollowupAsync("Failed to get user ID"); + return; + } + + Program.UserRepo.ClearUserAssociatedAddress(userId.Value); + await command.FollowupAsync("Done."); ; + } + } +} diff --git a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs b/Tools/BiblioTech/TokenCommands/EthAddressOption.cs index 6c351ba..3298ee8 100644 --- a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs +++ b/Tools/BiblioTech/TokenCommands/EthAddressOption.cs @@ -8,7 +8,8 @@ namespace BiblioTech.TokenCommands public EthAddressOption() : base(name: "ethaddress", description: "Ethereum address starting with '0x'.", - type: Discord.ApplicationCommandOptionType.String) + type: Discord.ApplicationCommandOptionType.String, + isRequired: true) { } diff --git a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs index add4c4a..000cfaa 100644 --- a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs @@ -1,5 +1,4 @@ using CodexContractsPlugin; -using CodexPlugin; using Core; using Discord.WebSocket; using GethPlugin; @@ -8,29 +7,36 @@ namespace BiblioTech.TokenCommands { public class GetBalanceCommand : BaseNetCommand { - private readonly EthAddressOption ethOption = new EthAddressOption(); + private readonly UserAssociateCommand userAssociateCommand; + private readonly UserOption optionalUser = new UserOption( + description: "If set, get balance for another user. (Optional, admin-only)", + isRequired: false); - public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) + public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci, UserAssociateCommand userAssociateCommand) : base(monitor, ci) { + this.userAssociateCommand = userAssociateCommand; } - // connect users to eth address! - public override string Name => "balance"; public override string StartingMessage => "Fetching balance..."; public override string Description => "Shows Eth and TestToken balance of an eth address."; - public override CommandOption[] Options => new[] { ethOption }; + public override CommandOption[] Options => new[] { optionalUser }; protected override async Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts) { - var addr = await ethOption.Parse(command); - if (addr == null) return; + var userId = GetUserId(optionalUser, command); + var addr = Program.UserRepo.GetCurrentAddressForUser(userId); + if (addr == null) + { + await command.FollowupAsync($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first."); + return; + } var eth = gethNode.GetEthBalance(addr); var testTokens = contracts.GetTestTokenBalance(gethNode, addr); - await command.RespondAsync($"Address '{addr.Address}' has {eth} and {testTokens}."); + await command.FollowupAsync($"{command.User.Username} has {eth} and {testTokens}."); } } } diff --git a/Tools/BiblioTech/TokenCommands/MintCommand.cs b/Tools/BiblioTech/TokenCommands/MintCommand.cs index c22c91a..330d233 100644 --- a/Tools/BiblioTech/TokenCommands/MintCommand.cs +++ b/Tools/BiblioTech/TokenCommands/MintCommand.cs @@ -10,7 +10,9 @@ namespace BiblioTech.TokenCommands private readonly string nl = Environment.NewLine; private readonly Ether defaultEthToSend = 10.Eth(); private readonly TestToken defaultTestTokensToMint = 1024.TestTokens(); - private readonly EthAddressOption ethOption = new EthAddressOption(); + private readonly UserOption optionalUser = new UserOption( + description: "If set, mint tokens for this user. (Optional, admin-only)", + isRequired: true); public MintCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) : base(monitor, ci) @@ -19,12 +21,13 @@ namespace BiblioTech.TokenCommands public override string Name => "mint"; public override string StartingMessage => "Minting some tokens..."; - public override string Description => "Mint some TestTokens and send some Eth to the address if its balance is low."; - public override CommandOption[] Options => new[] { ethOption }; + 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 }; protected override async Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts) { - var addr = await ethOption.Parse(command); + var userId = GetUserId(optionalUser, command); + var addr = Program.UserRepo.GetCurrentAddressForUser(userId); if (addr == null) return; var report = diff --git a/Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs b/Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs new file mode 100644 index 0000000..6cf58f1 --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs @@ -0,0 +1,34 @@ +using Discord.WebSocket; + +namespace BiblioTech.TokenCommands +{ + public class ReportHistoryCommand : BaseCommand + { + private readonly UserOption user = new UserOption( + description: "User to report history for.", + isRequired: true); + + public override string Name => "report"; + public override string StartingMessage => "Getting that data..."; + public override string Description => "Admin only. Reports bot-interaction history for a user."; + + protected override async Task Invoke(SocketSlashCommand command) + { + if (!IsSenderAdmin(command)) + { + await command.FollowupAsync("You're not an admin."); + return; + } + + var userId = user.GetOptionUserId(command); + if (userId == null) + { + await command.FollowupAsync("Failed to get user ID"); + return; + } + + var report = Program.UserRepo.GetInteractionReport(userId.Value); + await command.FollowupAsync(string.Join(Environment.NewLine, report)); + } + } +} diff --git a/Tools/BiblioTech/TokenCommands/ShowIdCommand.cs b/Tools/BiblioTech/TokenCommands/ShowIdCommand.cs new file mode 100644 index 0000000..39d9bf4 --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/ShowIdCommand.cs @@ -0,0 +1,16 @@ +using Discord.WebSocket; + +namespace BiblioTech.TokenCommands +{ + public class ShowIdCommand : BaseCommand + { + public override string Name => "my-id"; + public override string StartingMessage => "..."; + public override string Description => "Shows you your Discord ID. (Useful for admins)"; + + protected override async Task Invoke(SocketSlashCommand command) + { + await command.FollowupAsync("Your ID: " + command.User.Id); + } + } +} diff --git a/Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs b/Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs new file mode 100644 index 0000000..e989f33 --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs @@ -0,0 +1,33 @@ +using Discord.WebSocket; + +namespace BiblioTech.TokenCommands +{ + public class UserAssociateCommand : BaseCommand + { + private readonly EthAddressOption ethOption = new EthAddressOption(); + + public override string Name => "set"; + public override string StartingMessage => "hold on..."; + public override string Description => "Associates a Discord user with an Ethereum address in the TestNet. " + + "Warning: You can set your Ethereum address only once! Double-check before hitting enter."; + + public override CommandOption[] Options => new[] { ethOption }; + + protected override async Task Invoke(SocketSlashCommand command) + { + var userId = command.User.Id; + var data = await ethOption.Parse(command); + if (data == null) return; + + var currentAddress = Program.UserRepo.GetCurrentAddressForUser(userId); + if (currentAddress != null) + { + await command.FollowupAsync($"You've already set your Ethereum address to {currentAddress}."); + return; + } + + Program.UserRepo.AssociateUserWithAddress(userId, data); + await command.FollowupAsync("Done! Thank you for joining the test net!"); + } + } +} diff --git a/Tools/BiblioTech/TokenCommands/UserOption.cs b/Tools/BiblioTech/TokenCommands/UserOption.cs new file mode 100644 index 0000000..3f1628f --- /dev/null +++ b/Tools/BiblioTech/TokenCommands/UserOption.cs @@ -0,0 +1,22 @@ +using Discord; +using Discord.WebSocket; + +namespace BiblioTech.TokenCommands +{ + public class UserOption : CommandOption + { + public UserOption(string description, bool isRequired) + : base("user", description, ApplicationCommandOptionType.User, isRequired) + { + } + + public ulong? GetOptionUserId(SocketSlashCommand command) + { + var userOptionData = command.Data.Options.SingleOrDefault(o => o.Name == Name); + if (userOptionData == null) return null; + var user = userOptionData.Value as IUser; + if (user == null) return null; + return user.Id; + } + } +} diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index e4cfa2c..5348f99 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -42,6 +42,43 @@ namespace BiblioTech } } + public string[] GetInteractionReport(ulong discordId) + { + var result = new List(); + + lock (repoLock) + { + var filename = GetFilename(discordId); + if (!File.Exists(filename)) + { + result.Add("User has not joined the test net."); + } + else + { + var user = JsonConvert.DeserializeObject(File.ReadAllText(filename)); + if (user == null) + { + result.Add("Failed to load user records."); + } + else + { + result.Add("User joined on " + user.CreatedUtc.ToString("o")); + result.Add("Current address: " + user.CurrentAddress); + foreach (var ae in user.AssociateEvents) + { + result.Add($"{ae.Utc.ToString("o")} - Address set to: {ae.NewAddress}"); + } + foreach (var me in user.MintEvents) + { + result.Add($"{me.Utc.ToString("o")} - Minted {me.EthReceived} and {me.TestTokensMinted} to {me.UsedAddress}."); + } + } + } + } + + return result.ToArray(); + } + private void SetUserAddress(ulong discordId, EthAddress? address) { var user = GetOrCreate(discordId); From e16b1ce07912f638d201baddc07dbc2dc08b84c2 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 22 Oct 2023 10:38:46 +0200 Subject: [PATCH 14/18] Sets up guild role checking --- Tools/BiblioTech/AdminChecker.cs | 35 +++++++++++++++++++ Tools/BiblioTech/BaseCommand.cs | 4 +-- Tools/BiblioTech/CommandHandler.cs | 1 + .../ClearUserAssociationCommand.cs | 2 +- .../{ => Commands}/DeploymentsCommand.cs | 2 +- .../EthAddressOption.cs | 2 +- .../GetBalanceCommand.cs | 4 +-- .../MintCommand.cs | 2 +- .../ReportHistoryCommand.cs | 2 +- .../UserAssociateCommand.cs | 9 +++-- .../{TokenCommands => Commands}/UserOption.cs | 2 +- Tools/BiblioTech/DeploymentsFilesMonitor.cs | 5 +-- Tools/BiblioTech/Program.cs | 9 +++-- .../BiblioTech/TokenCommands/ShowIdCommand.cs | 16 --------- 14 files changed, 60 insertions(+), 35 deletions(-) create mode 100644 Tools/BiblioTech/AdminChecker.cs rename Tools/BiblioTech/{TokenCommands => Commands}/ClearUserAssociationCommand.cs (96%) rename Tools/BiblioTech/{ => Commands}/DeploymentsCommand.cs (96%) rename Tools/BiblioTech/{TokenCommands => Commands}/EthAddressOption.cs (96%) rename Tools/BiblioTech/{TokenCommands => Commands}/GetBalanceCommand.cs (96%) rename Tools/BiblioTech/{TokenCommands => Commands}/MintCommand.cs (98%) rename Tools/BiblioTech/{TokenCommands => Commands}/ReportHistoryCommand.cs (96%) rename Tools/BiblioTech/{TokenCommands => Commands}/UserAssociateCommand.cs (76%) rename Tools/BiblioTech/{TokenCommands => Commands}/UserOption.cs (94%) delete mode 100644 Tools/BiblioTech/TokenCommands/ShowIdCommand.cs diff --git a/Tools/BiblioTech/AdminChecker.cs b/Tools/BiblioTech/AdminChecker.cs new file mode 100644 index 0000000..684721d --- /dev/null +++ b/Tools/BiblioTech/AdminChecker.cs @@ -0,0 +1,35 @@ +using Discord.WebSocket; + +namespace BiblioTech +{ + public class AdminChecker + { + private SocketGuild guild = null!; + private ulong[] adminIds = Array.Empty(); + private DateTime lastUpdate = DateTime.MinValue; + + public void SetGuild(SocketGuild guild) + { + this.guild = guild; + } + + public bool IsUserAdmin(ulong userId) + { + if (ShouldUpdate()) UpdateAdminIds(); + + return adminIds.Contains(userId); + } + + private bool ShouldUpdate() + { + return !adminIds.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); + } + + private void UpdateAdminIds() + { + lastUpdate = DateTime.UtcNow; + var adminRole = guild.Roles.Single(r => r.Name == Program.Config.AdminRoleName); + adminIds = adminRole.Members.Select(m => m.Id).ToArray(); + } + } +} diff --git a/Tools/BiblioTech/BaseCommand.cs b/Tools/BiblioTech/BaseCommand.cs index 6086d34..066ade4 100644 --- a/Tools/BiblioTech/BaseCommand.cs +++ b/Tools/BiblioTech/BaseCommand.cs @@ -1,6 +1,6 @@ using Discord.WebSocket; using Discord; -using BiblioTech.TokenCommands; +using BiblioTech.Commands; namespace BiblioTech { @@ -37,7 +37,7 @@ namespace BiblioTech protected bool IsSenderAdmin(SocketSlashCommand command) { - + return Program.AdminChecker.IsUserAdmin(command.User.Id); } protected ulong GetUserId(UserOption userOption, SocketSlashCommand command) diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 4e5d8de..5726ee6 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -22,6 +22,7 @@ namespace BiblioTech private async Task Client_Ready() { var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName); + Program.AdminChecker.SetGuild(guild); var builders = commands.Select(c => { diff --git a/Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs b/Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs similarity index 96% rename from Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs rename to Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs index 10aa2b2..62eb649 100644 --- a/Tools/BiblioTech/TokenCommands/ClearUserAssociationCommand.cs +++ b/Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs @@ -1,6 +1,6 @@ using Discord.WebSocket; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class ClearUserAssociationCommand : BaseCommand { diff --git a/Tools/BiblioTech/DeploymentsCommand.cs b/Tools/BiblioTech/Commands/DeploymentsCommand.cs similarity index 96% rename from Tools/BiblioTech/DeploymentsCommand.cs rename to Tools/BiblioTech/Commands/DeploymentsCommand.cs index 414b0c4..3bc44f8 100644 --- a/Tools/BiblioTech/DeploymentsCommand.cs +++ b/Tools/BiblioTech/Commands/DeploymentsCommand.cs @@ -1,6 +1,6 @@ using Discord.WebSocket; -namespace BiblioTech +namespace BiblioTech.Commands { public class DeploymentsCommand : BaseCommand { diff --git a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs b/Tools/BiblioTech/Commands/EthAddressOption.cs similarity index 96% rename from Tools/BiblioTech/TokenCommands/EthAddressOption.cs rename to Tools/BiblioTech/Commands/EthAddressOption.cs index 3298ee8..e9e5784 100644 --- a/Tools/BiblioTech/TokenCommands/EthAddressOption.cs +++ b/Tools/BiblioTech/Commands/EthAddressOption.cs @@ -1,7 +1,7 @@ using Discord.WebSocket; using GethPlugin; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class EthAddressOption : CommandOption { diff --git a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs b/Tools/BiblioTech/Commands/GetBalanceCommand.cs similarity index 96% rename from Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs rename to Tools/BiblioTech/Commands/GetBalanceCommand.cs index 000cfaa..e51ab11 100644 --- a/Tools/BiblioTech/TokenCommands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/Commands/GetBalanceCommand.cs @@ -3,13 +3,13 @@ using Core; using Discord.WebSocket; using GethPlugin; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class GetBalanceCommand : BaseNetCommand { private readonly UserAssociateCommand userAssociateCommand; private readonly UserOption optionalUser = new UserOption( - description: "If set, get balance for another user. (Optional, admin-only)", + description: "If set, get balance for another user. (Optional, admin-only)", isRequired: false); public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci, UserAssociateCommand userAssociateCommand) diff --git a/Tools/BiblioTech/TokenCommands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs similarity index 98% rename from Tools/BiblioTech/TokenCommands/MintCommand.cs rename to Tools/BiblioTech/Commands/MintCommand.cs index 330d233..664cba8 100644 --- a/Tools/BiblioTech/TokenCommands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -3,7 +3,7 @@ using Core; using Discord.WebSocket; using GethPlugin; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class MintCommand : BaseNetCommand { diff --git a/Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs b/Tools/BiblioTech/Commands/ReportHistoryCommand.cs similarity index 96% rename from Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs rename to Tools/BiblioTech/Commands/ReportHistoryCommand.cs index 6cf58f1..61f5da8 100644 --- a/Tools/BiblioTech/TokenCommands/ReportHistoryCommand.cs +++ b/Tools/BiblioTech/Commands/ReportHistoryCommand.cs @@ -1,6 +1,6 @@ using Discord.WebSocket; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class ReportHistoryCommand : BaseCommand { diff --git a/Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs b/Tools/BiblioTech/Commands/UserAssociateCommand.cs similarity index 76% rename from Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs rename to Tools/BiblioTech/Commands/UserAssociateCommand.cs index e989f33..58fc32a 100644 --- a/Tools/BiblioTech/TokenCommands/UserAssociateCommand.cs +++ b/Tools/BiblioTech/Commands/UserAssociateCommand.cs @@ -1,10 +1,13 @@ using Discord.WebSocket; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class UserAssociateCommand : BaseCommand { private readonly EthAddressOption ethOption = new EthAddressOption(); + private readonly UserOption optionalUser = new UserOption( + description: "If set, associates Ethereum address for another user. (Optional, admin-only)", + isRequired: false); public override string Name => "set"; public override string StartingMessage => "hold on..."; @@ -15,12 +18,12 @@ namespace BiblioTech.TokenCommands protected override async Task Invoke(SocketSlashCommand command) { - var userId = command.User.Id; + var userId = GetUserId(optionalUser, command); var data = await ethOption.Parse(command); if (data == null) return; var currentAddress = Program.UserRepo.GetCurrentAddressForUser(userId); - if (currentAddress != null) + if (currentAddress != null && !IsSenderAdmin(command)) { await command.FollowupAsync($"You've already set your Ethereum address to {currentAddress}."); return; diff --git a/Tools/BiblioTech/TokenCommands/UserOption.cs b/Tools/BiblioTech/Commands/UserOption.cs similarity index 94% rename from Tools/BiblioTech/TokenCommands/UserOption.cs rename to Tools/BiblioTech/Commands/UserOption.cs index 3f1628f..e61558d 100644 --- a/Tools/BiblioTech/TokenCommands/UserOption.cs +++ b/Tools/BiblioTech/Commands/UserOption.cs @@ -1,7 +1,7 @@ using Discord; using Discord.WebSocket; -namespace BiblioTech.TokenCommands +namespace BiblioTech.Commands { public class UserOption : CommandOption { diff --git a/Tools/BiblioTech/DeploymentsFilesMonitor.cs b/Tools/BiblioTech/DeploymentsFilesMonitor.cs index ac44e58..d0d135b 100644 --- a/Tools/BiblioTech/DeploymentsFilesMonitor.cs +++ b/Tools/BiblioTech/DeploymentsFilesMonitor.cs @@ -10,10 +10,7 @@ namespace BiblioTech public CodexDeployment[] GetDeployments() { - if (ShouldUpdate()) - { - UpdateDeployments(); - } + if (ShouldUpdate()) UpdateDeployments(); return deployments; } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 395111f..725b304 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,5 +1,5 @@ using ArgsUniform; -using BiblioTech.TokenCommands; +using BiblioTech.Commands; using Core; using Discord; using Discord.WebSocket; @@ -14,6 +14,7 @@ namespace BiblioTech public static Configuration Config { get; private set; } = null!; public static DeploymentsFilesMonitor DeploymentFilesMonitor { get; } = new DeploymentsFilesMonitor(); public static UserRepo UserRepo { get; } = new UserRepo(); + public static AdminChecker AdminChecker { get; } = new AdminChecker(); public static Task Main(string[] args) { @@ -43,9 +44,13 @@ namespace BiblioTech var ci = entryPoint.CreateInterface(); + var associateCommand = new UserAssociateCommand(); var handler = new CommandHandler(client, - new GetBalanceCommand(monitor, ci), + new ClearUserAssociationCommand(), + new GetBalanceCommand(monitor, ci, associateCommand), new MintCommand(monitor, ci), + new ReportHistoryCommand(), + associateCommand, new DeploymentsCommand(monitor) ); diff --git a/Tools/BiblioTech/TokenCommands/ShowIdCommand.cs b/Tools/BiblioTech/TokenCommands/ShowIdCommand.cs deleted file mode 100644 index 39d9bf4..0000000 --- a/Tools/BiblioTech/TokenCommands/ShowIdCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Discord.WebSocket; - -namespace BiblioTech.TokenCommands -{ - public class ShowIdCommand : BaseCommand - { - public override string Name => "my-id"; - public override string StartingMessage => "..."; - public override string Description => "Shows you your Discord ID. (Useful for admins)"; - - protected override async Task Invoke(SocketSlashCommand command) - { - await command.FollowupAsync("Your ID: " + command.User.Id); - } - } -} From 8ef2e6023ea7dc9e3be157c26edc7a659871698b Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 22 Oct 2023 11:10:45 +0200 Subject: [PATCH 15/18] All works --- .../Commands/ClearUserAssociationCommand.cs | 1 + .../BiblioTech/Commands/DeploymentsCommand.cs | 2 +- Tools/BiblioTech/Commands/MintCommand.cs | 58 +++++++++++++------ .../Commands/ReportHistoryCommand.cs | 1 + .../Commands/UserAssociateCommand.cs | 6 +- Tools/BiblioTech/Configuration.cs | 3 + Tools/BiblioTech/Program.cs | 7 ++- 7 files changed, 54 insertions(+), 24 deletions(-) diff --git a/Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs b/Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs index 62eb649..cc49829 100644 --- a/Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs +++ b/Tools/BiblioTech/Commands/ClearUserAssociationCommand.cs @@ -11,6 +11,7 @@ namespace BiblioTech.Commands public override string Name => "clear"; public override string StartingMessage => "Hold on..."; public override string Description => "Admin only. Clears current Eth address for a user, allowing them to set a new one."; + public override CommandOption[] Options => new[] { user }; protected override async Task Invoke(SocketSlashCommand command) { diff --git a/Tools/BiblioTech/Commands/DeploymentsCommand.cs b/Tools/BiblioTech/Commands/DeploymentsCommand.cs index 3bc44f8..a3cca79 100644 --- a/Tools/BiblioTech/Commands/DeploymentsCommand.cs +++ b/Tools/BiblioTech/Commands/DeploymentsCommand.cs @@ -13,7 +13,7 @@ namespace BiblioTech.Commands public override string Name => "deployments"; public override string StartingMessage => "Fetching deployments information..."; - public override string Description => "Lists known deployments"; + public override string Description => "Lists active TestNet deployments"; protected override async Task Invoke(SocketSlashCommand command) { diff --git a/Tools/BiblioTech/Commands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs index 664cba8..31a5f8e 100644 --- a/Tools/BiblioTech/Commands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -7,16 +7,17 @@ namespace BiblioTech.Commands { public class MintCommand : BaseNetCommand { - private readonly string nl = Environment.NewLine; private readonly Ether defaultEthToSend = 10.Eth(); private readonly TestToken defaultTestTokensToMint = 1024.TestTokens(); private readonly UserOption optionalUser = new UserOption( description: "If set, mint tokens for this user. (Optional, admin-only)", - isRequired: true); + isRequired: false); + private readonly UserAssociateCommand userAssociateCommand; - public MintCommand(DeploymentsFilesMonitor monitor, CoreInterface ci) + public MintCommand(DeploymentsFilesMonitor monitor, CoreInterface ci, UserAssociateCommand userAssociateCommand) : base(monitor, ci) { + this.userAssociateCommand = userAssociateCommand; } public override string Name => "mint"; @@ -28,36 +29,57 @@ namespace BiblioTech.Commands { var userId = GetUserId(optionalUser, command); var addr = Program.UserRepo.GetCurrentAddressForUser(userId); - if (addr == null) return; + if (addr == null) + { + await command.FollowupAsync($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first."); + return; + } - var report = - ProcessEth(gethNode, addr) + - ProcessTestTokens(gethNode, contracts, addr); + var report = new List(); - await command.FollowupAsync(report); + var sentEth = ProcessEth(gethNode, addr, report); + var mintedTokens = ProcessTokens(gethNode, contracts, addr, report); + + Program.UserRepo.AddMintEventForUser(userId, addr, sentEth, mintedTokens); + + await command.FollowupAsync(string.Join(Environment.NewLine, report)); } - private string ProcessTestTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr) + private TestToken ProcessTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr, List report) { - var testTokens = contracts.GetTestTokenBalance(gethNode, addr); - if (testTokens.Amount < 64m) + if (ShouldMintTestTokens(gethNode, contracts, addr)) { contracts.MintTestTokens(gethNode, addr, defaultTestTokensToMint); - return $"Minted {defaultTestTokensToMint}." + nl; + report.Add($"Minted {defaultTestTokensToMint}."); + return defaultTestTokensToMint; } - return "TestToken balance over threshold." + nl; + + report.Add("TestToken balance over threshold."); + return 0.TestTokens(); } - private string ProcessEth(IGethNode gethNode, EthAddress addr) + private Ether ProcessEth(IGethNode gethNode, EthAddress addr, List report) { - var eth = gethNode.GetEthBalance(addr); - if (eth.Eth < 1.0m) + if (ShouldSendEth(gethNode, addr)) { gethNode.SendEth(addr, defaultEthToSend); - return $"Sent {defaultEthToSend}." + nl; + report.Add($"Sent {defaultEthToSend}."); + return defaultEthToSend; } + report.Add("Eth balance is over threshold."); + return 0.Eth(); + } - return "Eth balance over threshold." + nl; + private bool ShouldMintTestTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr) + { + var testTokens = contracts.GetTestTokenBalance(gethNode, addr); + return testTokens.Amount < 64m; + } + + private bool ShouldSendEth(IGethNode gethNode, EthAddress addr) + { + var eth = gethNode.GetEthBalance(addr); + return eth.Eth < 1.0m; } } } diff --git a/Tools/BiblioTech/Commands/ReportHistoryCommand.cs b/Tools/BiblioTech/Commands/ReportHistoryCommand.cs index 61f5da8..ccad6f7 100644 --- a/Tools/BiblioTech/Commands/ReportHistoryCommand.cs +++ b/Tools/BiblioTech/Commands/ReportHistoryCommand.cs @@ -11,6 +11,7 @@ namespace BiblioTech.Commands public override string Name => "report"; public override string StartingMessage => "Getting that data..."; public override string Description => "Admin only. Reports bot-interaction history for a user."; + public override CommandOption[] Options => new[] { user }; protected override async Task Invoke(SocketSlashCommand command) { diff --git a/Tools/BiblioTech/Commands/UserAssociateCommand.cs b/Tools/BiblioTech/Commands/UserAssociateCommand.cs index 58fc32a..8ab25f2 100644 --- a/Tools/BiblioTech/Commands/UserAssociateCommand.cs +++ b/Tools/BiblioTech/Commands/UserAssociateCommand.cs @@ -11,10 +11,8 @@ namespace BiblioTech.Commands public override string Name => "set"; public override string StartingMessage => "hold on..."; - public override string Description => "Associates a Discord user with an Ethereum address in the TestNet. " + - "Warning: You can set your Ethereum address only once! Double-check before hitting enter."; - - public override CommandOption[] Options => new[] { ethOption }; + public override string Description => "Associates a Discord user with an Ethereum address."; + public override CommandOption[] Options => new CommandOption[] { ethOption, optionalUser }; protected override async Task Invoke(SocketSlashCommand command) { diff --git a/Tools/BiblioTech/Configuration.cs b/Tools/BiblioTech/Configuration.cs index 00b7979..decf006 100644 --- a/Tools/BiblioTech/Configuration.cs +++ b/Tools/BiblioTech/Configuration.cs @@ -15,5 +15,8 @@ namespace BiblioTech [Uniform("userdata", "u", "USERDATA", false, "Path where user data files will be saved.")] public string UserDataPath { get; set; } = "userdata"; + + [Uniform("admin-role", "a", "ADMINROLE", true, "Name of the Discord server admin role")] + public string AdminRoleName { get; set; } = string.Empty; } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 725b304..3a8d997 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -21,6 +21,11 @@ namespace BiblioTech var uniformArgs = new ArgsUniform(PrintHelp, args); Config = uniformArgs.Parse(); + if (!Directory.Exists(Config.UserDataPath)) + { + Directory.CreateDirectory(Config.UserDataPath); + } + return new Program().MainAsync(); } @@ -48,7 +53,7 @@ namespace BiblioTech var handler = new CommandHandler(client, new ClearUserAssociationCommand(), new GetBalanceCommand(monitor, ci, associateCommand), - new MintCommand(monitor, ci), + new MintCommand(monitor, ci, associateCommand), new ReportHistoryCommand(), associateCommand, new DeploymentsCommand(monitor) From 116f62e73e666111b84a52d38b89e4c6345ef2a6 Mon Sep 17 00:00:00 2001 From: benbierens Date: Sun, 22 Oct 2023 11:26:00 +0200 Subject: [PATCH 16/18] Adds eth address validation. --- Tools/BiblioTech/Commands/EthAddressOption.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Tools/BiblioTech/Commands/EthAddressOption.cs b/Tools/BiblioTech/Commands/EthAddressOption.cs index e9e5784..6996c79 100644 --- a/Tools/BiblioTech/Commands/EthAddressOption.cs +++ b/Tools/BiblioTech/Commands/EthAddressOption.cs @@ -1,5 +1,6 @@ using Discord.WebSocket; using GethPlugin; +using Nethereum.Util; namespace BiblioTech.Commands { @@ -18,14 +19,21 @@ namespace BiblioTech.Commands var ethOptionData = command.Data.Options.SingleOrDefault(o => o.Name == Name); if (ethOptionData == null) { - await command.RespondAsync("EthAddress option not received."); + await command.FollowupAsync("EthAddress option not received."); return null; } var ethAddressStr = ethOptionData.Value as string; if (string.IsNullOrEmpty(ethAddressStr)) { - // todo, validate that it is an eth address. - await command.RespondAsync("EthAddress is null or invalid."); + await command.FollowupAsync("EthAddress is null or empty."); + return null; + } + + if (!AddressUtil.Current.IsValidAddressLength(ethAddressStr) || + !AddressUtil.Current.IsValidEthereumAddressHexFormat(ethAddressStr) || + !AddressUtil.Current.IsChecksumAddress(ethAddressStr)) + { + await command.FollowupAsync("EthAddress is not valid."); return null; } From e11a7d160045ef8d97bbb3439f4610672005d47c Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 23 Oct 2023 10:19:52 +0200 Subject: [PATCH 17/18] Gives deployments a name. --- ProjectPlugins/CodexPlugin/CodexDeployment.cs | 4 +++- Tests/CodexContinuousTests/deploy-and-run.sh | 1 + Tools/BiblioTech/Commands/DeploymentsCommand.cs | 11 +++++++++-- Tools/BiblioTech/docker/docker-compose.yaml | 1 + Tools/CodexNetDeployer/Configuration.cs | 3 +++ Tools/CodexNetDeployer/Deployer.cs | 1 + Tools/CodexNetDeployer/deploy-continuous-testnet.sh | 1 + 7 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ProjectPlugins/CodexPlugin/CodexDeployment.cs b/ProjectPlugins/CodexPlugin/CodexDeployment.cs index f63d8bb..b0b50c0 100644 --- a/ProjectPlugins/CodexPlugin/CodexDeployment.cs +++ b/ProjectPlugins/CodexPlugin/CodexDeployment.cs @@ -24,8 +24,9 @@ namespace CodexPlugin public class DeploymentMetadata { - public DeploymentMetadata(DateTime startUtc, DateTime finishedUtc, string kubeNamespace, int numberOfCodexNodes, int numberOfValidators, int storageQuotaMB, CodexLogLevel codexLogLevel, int initialTestTokens, int minPrice, int maxCollateral, int maxDuration, int blockTTL, int blockMI, int blockMN) + public DeploymentMetadata(string name, DateTime startUtc, DateTime finishedUtc, string kubeNamespace, int numberOfCodexNodes, int numberOfValidators, int storageQuotaMB, CodexLogLevel codexLogLevel, int initialTestTokens, int minPrice, int maxCollateral, int maxDuration, int blockTTL, int blockMI, int blockMN) { + Name = name; StartUtc = startUtc; FinishedUtc = finishedUtc; KubeNamespace = kubeNamespace; @@ -42,6 +43,7 @@ namespace CodexPlugin BlockMN = blockMN; } + public string Name { get; } public DateTime StartUtc { get; } public DateTime FinishedUtc { get; } public string KubeNamespace { get; } diff --git a/Tests/CodexContinuousTests/deploy-and-run.sh b/Tests/CodexContinuousTests/deploy-and-run.sh index 1fa5037..dfc5e10 100644 --- a/Tests/CodexContinuousTests/deploy-and-run.sh +++ b/Tests/CodexContinuousTests/deploy-and-run.sh @@ -9,6 +9,7 @@ cd ../../Tools/CodexNetDeployer for i in $( seq 0 $replication) do dotnet run \ + --deploy-name=codex-continuous-$name-$i \ --kube-config=/opt/kubeconfig.yaml \ --kube-namespace=codex-continuous-$name-tests-$i \ --deploy-file=codex-deployment-$name-$i.json \ diff --git a/Tools/BiblioTech/Commands/DeploymentsCommand.cs b/Tools/BiblioTech/Commands/DeploymentsCommand.cs index a3cca79..50dc95f 100644 --- a/Tools/BiblioTech/Commands/DeploymentsCommand.cs +++ b/Tools/BiblioTech/Commands/DeploymentsCommand.cs @@ -1,4 +1,5 @@ -using Discord.WebSocket; +using CodexPlugin; +using Discord.WebSocket; namespace BiblioTech.Commands { @@ -25,7 +26,13 @@ namespace BiblioTech.Commands return; } - await command.FollowupAsync($"Deployments: {string.Join(", ", deployments.Select(d => d.Metadata.StartUtc.ToString("o")))}"); + await command.FollowupAsync($"Deployments: {string.Join(", ", deployments.Select(FormatDeployment))}"); + } + + private string FormatDeployment(CodexDeployment deployment) + { + var m = deployment.Metadata; + return $"{m.Name} ({m.StartUtc.ToString("o")})"; } } } diff --git a/Tools/BiblioTech/docker/docker-compose.yaml b/Tools/BiblioTech/docker/docker-compose.yaml index dae80e4..2eaff90 100644 --- a/Tools/BiblioTech/docker/docker-compose.yaml +++ b/Tools/BiblioTech/docker/docker-compose.yaml @@ -4,3 +4,4 @@ services: environment: - TOKEN=tokenplz - SERVERNAME=ThatBen's server + - ADMINROLE=adminers diff --git a/Tools/CodexNetDeployer/Configuration.cs b/Tools/CodexNetDeployer/Configuration.cs index 3a371cd..f37e342 100644 --- a/Tools/CodexNetDeployer/Configuration.cs +++ b/Tools/CodexNetDeployer/Configuration.cs @@ -8,6 +8,9 @@ namespace CodexNetDeployer public const int SecondsIn1Day = 24 * 60 * 60; public const int TenMinutes = 10 * 60; + [Uniform("deploy-name", "nm", "DEPLOYNAME", false, "Name of the deployment. (optional)")] + public string DeploymentName { get; set; } = "unnamed"; + [Uniform("kube-config", "kc", "KUBECONFIG", false, "Path to Kubeconfig file. Use 'null' (default) to use local cluster.")] public string KubeConfigFile { get; set; } = "null"; diff --git a/Tools/CodexNetDeployer/Deployer.cs b/Tools/CodexNetDeployer/Deployer.cs index 6dd3e6c..6aa0945 100644 --- a/Tools/CodexNetDeployer/Deployer.cs +++ b/Tools/CodexNetDeployer/Deployer.cs @@ -151,6 +151,7 @@ namespace CodexNetDeployer private DeploymentMetadata CreateMetadata(DateTime startUtc) { return new DeploymentMetadata( + name: config.DeploymentName, startUtc: startUtc, finishedUtc: DateTime.UtcNow, kubeNamespace: config.KubeNamespace, diff --git a/Tools/CodexNetDeployer/deploy-continuous-testnet.sh b/Tools/CodexNetDeployer/deploy-continuous-testnet.sh index 8036273..2fe63a5 100644 --- a/Tools/CodexNetDeployer/deploy-continuous-testnet.sh +++ b/Tools/CodexNetDeployer/deploy-continuous-testnet.sh @@ -1,4 +1,5 @@ dotnet run \ + --deploy-name=codex-continuous-test-deployment \ --kube-config=/opt/kubeconfig.yaml \ --kube-namespace=codex-continuous-tests \ --deploy-file=codex-deployment.json \ From 4adce837ecd794a898984d0b1ee9cdebbcac9291 Mon Sep 17 00:00:00 2001 From: benbierens Date: Mon, 23 Oct 2023 10:32:11 +0200 Subject: [PATCH 18/18] Logs total run duration in overview log. --- Tests/CodexContinuousTests/ContinuousTestRunner.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/CodexContinuousTests/ContinuousTestRunner.cs b/Tests/CodexContinuousTests/ContinuousTestRunner.cs index af288a2..02bb35e 100644 --- a/Tests/CodexContinuousTests/ContinuousTestRunner.cs +++ b/Tests/CodexContinuousTests/ContinuousTestRunner.cs @@ -92,6 +92,7 @@ namespace ContinuousTests { var testDuration = Time.FormatDuration(DateTime.UtcNow - startTime); var testData = FormatTestRuns(testLoops); + overviewLog.Log("Total duration: " + testDuration); if (config.TargetDurationSeconds > 0) {