diff --git a/ProjectPlugins/GethPlugin/GethNode.cs b/ProjectPlugins/GethPlugin/GethNode.cs index 050074a..36ae392 100644 --- a/ProjectPlugins/GethPlugin/GethNode.cs +++ b/ProjectPlugins/GethPlugin/GethNode.cs @@ -22,11 +22,11 @@ namespace GethPlugin GethBootstrapNode GetBootstrapRecord(); } - public class GethNode : IGethNode + public class DeploymentGethNode : BaseGethNode, IGethNode { private readonly ILog log; - public GethNode(ILog log, GethDeployment startResult) + public DeploymentGethNode(ILog log, GethDeployment startResult) { this.log = log; StartResult = startResult; @@ -35,6 +35,59 @@ namespace GethPlugin public GethDeployment StartResult { get; } public RunningContainer Container => StartResult.Container; + public GethBootstrapNode GetBootstrapRecord() + { + var address = StartResult.Container.GetInternalAddress(GethContainerRecipe.ListenPortTag); + + return new GethBootstrapNode( + publicKey: StartResult.PubKey, + ipAddress: address.Host.Replace("http://", ""), + port: address.Port + ); + } + + protected override NethereumInteraction StartInteraction() + { + var address = StartResult.Container.GetAddress(log, GethContainerRecipe.HttpPortTag); + var account = StartResult.Account; + + var creator = new NethereumInteractionCreator(log, address.Host, address.Port, account.PrivateKey); + return creator.CreateWorkflow(); + } + } + + public class CustomGethNode : BaseGethNode, IGethNode + { + private readonly ILog log; + private readonly string gethHost; + private readonly int gethPort; + private readonly string privateKey; + + public GethDeployment StartResult => throw new NotImplementedException(); + public RunningContainer Container => throw new NotImplementedException(); + + public CustomGethNode(ILog log, string gethHost, int gethPort, string privateKey) + { + this.log = log; + this.gethHost = gethHost; + this.gethPort = gethPort; + this.privateKey = privateKey; + } + + public GethBootstrapNode GetBootstrapRecord() + { + throw new NotImplementedException(); + } + + protected override NethereumInteraction StartInteraction() + { + var creator = new NethereumInteractionCreator(log, gethHost, gethPort, privateKey); + return creator.CreateWorkflow(); + } + } + + public abstract class BaseGethNode + { public Ether GetEthBalance() { return StartInteraction().GetEthBalance().Eth(); @@ -70,26 +123,6 @@ namespace GethPlugin StartInteraction().SendTransaction(contractAddress, function); } - public GethBootstrapNode GetBootstrapRecord() - { - var address = StartResult.Container.GetInternalAddress(GethContainerRecipe.ListenPortTag); - - return new GethBootstrapNode( - publicKey: StartResult.PubKey, - ipAddress: address.Host.Replace("http://", ""), - port: address.Port - ); - } - - private NethereumInteraction StartInteraction() - { - var address = StartResult.Container.GetAddress(log, GethContainerRecipe.HttpPortTag); - var account = StartResult.Account; - - var creator = new NethereumInteractionCreator(log, address.Host, address.Port, account.PrivateKey); - return creator.CreateWorkflow(); - } - public decimal? GetSyncedBlockNumber() { return StartInteraction().GetSyncedBlockNumber(); @@ -99,5 +132,7 @@ namespace GethPlugin { return StartInteraction().IsContractAvailable(abi, contractAddress); } + + protected abstract NethereumInteraction StartInteraction(); } } diff --git a/ProjectPlugins/GethPlugin/GethStarter.cs b/ProjectPlugins/GethPlugin/GethStarter.cs index 5287d9c..c5ad801 100644 --- a/ProjectPlugins/GethPlugin/GethStarter.cs +++ b/ProjectPlugins/GethPlugin/GethStarter.cs @@ -44,7 +44,7 @@ namespace GethPlugin public IGethNode WrapGethContainer(GethDeployment startResult) { startResult = SerializeGate.Gate(startResult); - return new GethNode(tools.GetLog(), startResult); + return new DeploymentGethNode(tools.GetLog(), startResult); } private void Log(string msg) diff --git a/Tools/BiblioTech/BaseDeploymentCommand.cs b/Tools/BiblioTech/BaseDeploymentCommand.cs deleted file mode 100644 index 100c39f..0000000 --- a/Tools/BiblioTech/BaseDeploymentCommand.cs +++ /dev/null @@ -1,36 +0,0 @@ -using BiblioTech.Options; -using CodexPlugin; - -namespace BiblioTech -{ - public abstract class BaseDeploymentCommand : BaseCommand - { - protected override async Task Invoke(CommandContext context) - { - var proceed = await OnInvoke(context); - if (!proceed) return; - - var deployments = Program.DeploymentFilesMonitor.GetDeployments(); - if (deployments.Length == 0) - { - await context.Followup("No deployments are currently available."); - return; - } - if (deployments.Length > 1) - { - await context.Followup("Multiple deployments are online. I don't know which one to pick!"); - return; - } - - var codexDeployment = deployments.Single(); - await ExecuteDeploymentCommand(context, codexDeployment); - } - - protected abstract Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment); - - protected virtual Task OnInvoke(CommandContext context) - { - return Task.FromResult(true); - } - } -} diff --git a/Tools/BiblioTech/BaseGethCommand.cs b/Tools/BiblioTech/BaseGethCommand.cs index 37841b1..52bdb45 100644 --- a/Tools/BiblioTech/BaseGethCommand.cs +++ b/Tools/BiblioTech/BaseGethCommand.cs @@ -1,27 +1,89 @@ using BiblioTech.Options; using CodexContractsPlugin; -using CodexPlugin; -using Core; using GethPlugin; +using Logging; namespace BiblioTech { - public abstract class BaseGethCommand : BaseDeploymentCommand + public static class GethInput { - private readonly CoreInterface ci; + private const string GethHostVar = "GETH_HOST"; + private const string GethPortVar = "GETH_HTTP_PORT"; + private const string GethPrivKeyVar = "GETH_PRIVATE_KEY"; + private const string MarketplaceAddressVar = "CODEXCONTRACTS_MARKETPLACEADDRESS"; + private const string TokenAddressVar = "CODEXCONTRACTS_TOKENADDRESS"; + private const string AbiVar = "CODEXCONTRACTS_ABI"; - public BaseGethCommand(CoreInterface ci) + static GethInput() { - this.ci = ci; + var error = new List(); + var gethHost = GetEnvVar(error, GethHostVar); + var gethPort = Convert.ToInt32(GetEnvVar(error, GethPortVar)); + var privateKey = GetEnvVar(error, GethPrivKeyVar); + var marketplaceAddress = GetEnvVar(error, MarketplaceAddressVar); + var tokenAddress = GetEnvVar(error, TokenAddressVar); + var abi = GetEnvVar(error, AbiVar); + + if (error.Any()) + { + LoadError = string.Join(", ", error); + } + else + { + GethHost = gethHost!; + GethPort = gethPort; + PrivateKey = privateKey!; + MarketplaceAddress = marketplaceAddress!; + TokenAddress = tokenAddress!; + ABI = abi!; + } } - protected override async Task ExecuteDeploymentCommand(CommandContext context, CodexDeployment codexDeployment) - { - var gethDeployment = codexDeployment.GethDeployment; - var contractsDeployment = codexDeployment.CodexContractsDeployment; + public static string GethHost { get; } = string.Empty; + public static int GethPort { get; } + public static string PrivateKey { get; } = string.Empty; + public static string MarketplaceAddress { get; } = string.Empty; + public static string TokenAddress { get; } = string.Empty; + public static string ABI { get; } = string.Empty; + public static string LoadError { get; } = string.Empty; - var gethNode = ci.WrapGethDeployment(gethDeployment); - var contracts = ci.WrapCodexContractsDeployment(gethNode, contractsDeployment); + private static string? GetEnvVar(List error, string name) + { + var result = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(result)) error.Add($"'{name}' is not set."); + return result; + } + } + + public abstract class BaseGethCommand : BaseCommand + { + protected override async Task Invoke(CommandContext context) + { + var log = new ConsoleLog(); + + if (!string.IsNullOrEmpty(GethInput.LoadError)) + { + var msg = "Geth input incorrect: " + GethInput.LoadError; + log.Error(msg); + if (IsInAdminChannel(context.Command)) + { + await context.Followup(msg); + } + else + { + await context.Followup("I'm sorry, there seems to be a configuration error."); + } + return; + } + + var contractsDeployment = new CodexContractsDeployment( + marketplaceAddress: GethInput.MarketplaceAddress, + abi: GethInput.ABI, + tokenAddress: GethInput.TokenAddress + ); + + var gethNode = new CustomGethNode(log, GethInput.GethHost, GethInput.GethPort, GethInput.PrivateKey); + var contracts = new CodexContractsAccess(log, gethNode, contractsDeployment); await Execute(context, gethNode, contracts); } diff --git a/Tools/BiblioTech/Commands/AdminCommand.cs b/Tools/BiblioTech/Commands/AdminCommand.cs index 5e4d478..3cb0ae8 100644 --- a/Tools/BiblioTech/Commands/AdminCommand.cs +++ b/Tools/BiblioTech/Commands/AdminCommand.cs @@ -9,20 +9,13 @@ namespace BiblioTech.Commands { private readonly ClearUserAssociationCommand clearCommand = new ClearUserAssociationCommand(); private readonly ReportCommand reportCommand = new ReportCommand(); - private readonly DeployListCommand deployListCommand = new DeployListCommand(); - private readonly DeployUploadCommand deployUploadCommand = new DeployUploadCommand(); - private readonly DeployRemoveCommand deployRemoveCommand = new DeployRemoveCommand(); private readonly WhoIsCommand whoIsCommand = new WhoIsCommand(); - private readonly NetInfoCommand netInfoCommand; - private readonly DebugPeerCommand debugPeerCommand; private readonly AddSprCommand addSprCommand; private readonly ClearSprsCommand clearSprsCommand; private readonly GetSprCommand getSprCommand; - public AdminCommand(CoreInterface ci, SprCommand sprCommand) + public AdminCommand(SprCommand sprCommand) { - netInfoCommand = new NetInfoCommand(ci); - debugPeerCommand = new DebugPeerCommand(ci); addSprCommand = new AddSprCommand(sprCommand); clearSprsCommand = new ClearSprsCommand(sprCommand); getSprCommand = new GetSprCommand(sprCommand); @@ -36,12 +29,7 @@ namespace BiblioTech.Commands { clearCommand, reportCommand, - deployListCommand, - deployUploadCommand, - deployRemoveCommand, whoIsCommand, - netInfoCommand, - debugPeerCommand, addSprCommand, clearSprsCommand, getSprCommand @@ -63,15 +51,9 @@ namespace BiblioTech.Commands await clearCommand.CommandHandler(context); await reportCommand.CommandHandler(context); - await deployListCommand.CommandHandler(context); - await deployUploadCommand.CommandHandler(context); - await deployRemoveCommand.CommandHandler(context); await whoIsCommand.CommandHandler(context); - await netInfoCommand.CommandHandler(context); - await debugPeerCommand.CommandHandler(context); await addSprCommand.CommandHandler(context); await clearSprsCommand.CommandHandler(context); - await deployUploadCommand.CommandHandler(context); } public class ClearUserAssociationCommand : SubCommandOption @@ -126,95 +108,6 @@ namespace BiblioTech.Commands } } - public class DeployListCommand : SubCommandOption - { - public DeployListCommand() - : base("list", "Lists current deployments.") - { - } - - protected override async Task onSubCommand(CommandContext context) - { - var deployments = Program.DeploymentFilesMonitor.GetDeployments(); - if (!deployments.Any()) - { - await context.Followup("No deployments available."); - return; - } - - var nl = Environment.NewLine; - await context.Followup(deployments.Select(FormatDeployment).ToArray()); - } - - private string FormatDeployment(CodexDeployment deployment) - { - var m = deployment.Metadata; - return $"'{m.Name}' ({m.StartUtc.ToString("o")})"; - } - } - - public class DeployUploadCommand : SubCommandOption - { - private readonly FileAttachementOption fileOption = new FileAttachementOption( - name: "json", - description: "Codex-deployment json to add.", - isRequired: true); - - public DeployUploadCommand() - : base("add", "Upload a new deployment JSON file.") - { - } - - public override CommandOption[] Options => new[] { fileOption }; - - protected override async Task onSubCommand(CommandContext context) - { - var file = await fileOption.Parse(context); - if (file == null) return; - - var result = await Program.DeploymentFilesMonitor.DownloadDeployment(file); - if (result) - { - await context.Followup("Success!"); - } - else - { - await context.Followup("That didn't work."); - } - } - } - - public class DeployRemoveCommand : SubCommandOption - { - private readonly StringOption stringOption = new StringOption( - name: "name", - description: "Name of deployment to remove.", - isRequired: true); - - public DeployRemoveCommand() - : base("remove", "Removes a deployment file.") - { - } - - public override CommandOption[] Options => new[] { stringOption }; - - protected override async Task onSubCommand(CommandContext context) - { - var str = await stringOption.Parse(context); - if (string.IsNullOrEmpty(str)) return; - - var result = Program.DeploymentFilesMonitor.DeleteDeployment(str); - if (result) - { - await context.Followup("Success!"); - } - else - { - await context.Followup("That didn't work."); - } - } - } - public class WhoIsCommand : SubCommandOption { private readonly UserOption userOption = new UserOption("User", isRequired: false); @@ -248,152 +141,6 @@ namespace BiblioTech.Commands } } - public abstract class AdminDeploymentCommand : SubCommandOption - { - private readonly CoreInterface ci; - - public AdminDeploymentCommand(CoreInterface ci, string name, string description) - : base(name, description) - { - this.ci = ci; - } - - protected async Task OnDeployment(CommandContext context, Func action) - { - var deployment = Program.DeploymentFilesMonitor.GetDeployments().SingleOrDefault(); - if (deployment == null) - { - await context.Followup("No deployment found."); - return default; - } - if (deployment.CodexInstances == null || !deployment.CodexInstances.Any()) - { - await context.Followup("No codex instances were deployed."); - return default; - } - - try - { - var group = ci.WrapCodexContainers(deployment.CodexInstances.Select(i => i.Containers).ToArray()); - var result = action(group, deployment.Metadata.Name); - return result; - } - catch (Exception ex) - { - var message = new[] - { - "Failed to wrap nodes with exception: " - }; - var exceptionMessage = ex.ToString().Split(Environment.NewLine); - - await context.Followup(message.Concat(exceptionMessage).ToArray()); - return default; - } - } - } - - public class NetInfoCommand : AdminDeploymentCommand - { - public NetInfoCommand(CoreInterface ci) - : base(ci, name: "netinfo", - description: "Fetches info endpoints of codex nodes.") - { - } - - protected override async Task onSubCommand(CommandContext context) - { - var report = await OnDeployment(context, CreateNetInfoReport); - if (report != null && report.Any()) - { - await context.Followup(report); - } - } - - private string[] CreateNetInfoReport(ICodexNodeGroup group, string name) - { - var content = new List - { - $"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes.", - $"Deployment name: '{name}'." - }; - - foreach (var node in group) - { - try - { - var info = node.GetDebugInfo(); - var json = JsonConvert.SerializeObject(info, Formatting.Indented); - var jsonLines = json.Split(Environment.NewLine); - content.Add($"Node '{node.GetName()}' responded with:"); - content.Add("---"); - content.AddRange(jsonLines); - content.Add("---"); - } - catch (Exception ex) - { - content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex); - } - } - - return content.ToArray(); - } - } - - public class DebugPeerCommand : AdminDeploymentCommand - { - private readonly StringOption peerIdOption = new StringOption("peerid", "id of peer to try and reach.", true); - - public DebugPeerCommand(CoreInterface ci) - : base(ci, name: "debugpeer", - description: "Calls debug/peer on each codex node.") - { - } - - public override CommandOption[] Options => new[] { peerIdOption }; - - protected override async Task onSubCommand(CommandContext context) - { - var peerId = await peerIdOption.Parse(context); - if (string.IsNullOrEmpty(peerId)) return; - - var report = await OnDeployment(context, (group, name) => CreateDebugPeerReport(group, name, peerId)); - if (report != null && report.Any()) - { - await context.Followup(report); - } - } - - private string[] CreateDebugPeerReport(ICodexNodeGroup group, string name, string peerId) - { - var content = new List - { - $"{DateTime.UtcNow.ToString("o")} - {group.Count()} Codex nodes.", - $"Deployment name: '{name}'.", - $"Calling debug/peer for '{peerId}'." - }; - - foreach (var node in group) - { - try - { - var info = node.GetDebugPeer(peerId); - var json = JsonConvert.SerializeObject(info, Formatting.Indented); - var jsonLines = json.Split(Environment.NewLine); - content.Add($"Node '{node.GetName()}' responded with:"); - content.Add("---"); - content.AddRange(jsonLines); - content.Add("---"); - } - catch (Exception ex) - { - content.Add($"Node '{node.GetName()}' failed to respond with exception: " + ex); - } - } - - return content.ToArray(); - } - } - public class AddSprCommand : SubCommandOption { private readonly SprCommand sprCommand; diff --git a/Tools/BiblioTech/Commands/GetBalanceCommand.cs b/Tools/BiblioTech/Commands/GetBalanceCommand.cs index 05337ab..60118f5 100644 --- a/Tools/BiblioTech/Commands/GetBalanceCommand.cs +++ b/Tools/BiblioTech/Commands/GetBalanceCommand.cs @@ -1,5 +1,6 @@ using BiblioTech.Options; using CodexContractsPlugin; +using CodexPlugin; using Core; using GethPlugin; @@ -12,8 +13,7 @@ namespace BiblioTech.Commands description: "If set, get balance for another user. (Optional, admin-only)", isRequired: false); - public GetBalanceCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand) - : base(ci) + public GetBalanceCommand(UserAssociateCommand userAssociateCommand) { this.userAssociateCommand = userAssociateCommand; } diff --git a/Tools/BiblioTech/Commands/MintCommand.cs b/Tools/BiblioTech/Commands/MintCommand.cs index ed87a1e..cc50a2a 100644 --- a/Tools/BiblioTech/Commands/MintCommand.cs +++ b/Tools/BiblioTech/Commands/MintCommand.cs @@ -1,6 +1,5 @@ using BiblioTech.Options; using CodexContractsPlugin; -using Core; using GethPlugin; namespace BiblioTech.Commands @@ -14,8 +13,7 @@ namespace BiblioTech.Commands isRequired: false); private readonly UserAssociateCommand userAssociateCommand; - public MintCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand) - : base(ci) + public MintCommand(UserAssociateCommand userAssociateCommand) { this.userAssociateCommand = userAssociateCommand; } diff --git a/Tools/BiblioTech/Commands/UserAssociateCommand.cs b/Tools/BiblioTech/Commands/UserAssociateCommand.cs index 2ede721..c23718c 100644 --- a/Tools/BiblioTech/Commands/UserAssociateCommand.cs +++ b/Tools/BiblioTech/Commands/UserAssociateCommand.cs @@ -27,8 +27,6 @@ namespace BiblioTech.Commands return; } - // private commands - var result = Program.UserRepo.AssociateUserWithAddress(user, data); if (result) { diff --git a/Tools/BiblioTech/DeploymentsFilesMonitor.cs b/Tools/BiblioTech/DeploymentsFilesMonitor.cs deleted file mode 100644 index 3b1e52c..0000000 --- a/Tools/BiblioTech/DeploymentsFilesMonitor.cs +++ /dev/null @@ -1,119 +0,0 @@ -using CodexPlugin; -using Discord; -using Newtonsoft.Json; -using System.Diagnostics.CodeAnalysis; - -namespace BiblioTech -{ - public class DeploymentsFilesMonitor - { - private readonly List deployments = new List(); - - public void Initialize() - { - LoadDeployments(); - } - - public CodexDeployment[] GetDeployments() - { - return deployments.ToArray(); - } - - public async Task DownloadDeployment(IAttachment file) - { - using var http = new HttpClient(); - var response = await http.GetAsync(file.Url); - var str = await response.Content.ReadAsStringAsync(); - if (string.IsNullOrEmpty(str)) return false; - - try - { - var deploy = JsonConvert.DeserializeObject(str); - var names = deployments.Select(d => d.Metadata.Name).ToArray(); - if (deploy != null && IsDeploymentOk(deploy) && !names.Contains(deploy.Metadata.Name)) - { - var targetFile = Path.Combine(Program.Config.EndpointsPath, Guid.NewGuid().ToString().ToLowerInvariant() + ".json"); - File.WriteAllText(targetFile, str); - LoadDeployments(); - return true; - } - } - catch { } - return false; - } - - public bool DeleteDeployment(string deploymentName) - { - var path = Program.Config.EndpointsPath; - if (!Directory.Exists(path)) return false; - var files = Directory.GetFiles(path); - - foreach (var file in files) - { - var deploy = ProcessFile(file); - if (deploy != null && deploy.Metadata.Name == deploymentName) - { - File.Delete(file); - LoadDeployments(); - return true; - } - } - return false; - } - - private bool IsDeploymentOk(CodexDeployment? deploy) - { - if (deploy == null) return false; - if (deploy.GethDeployment == null) return false; - if (deploy.GethDeployment.Containers == null) return false; - return true; - } - - private void LoadDeployments() - { - deployments.Clear(); - 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.AddRange(files - .Select(ProcessFile) - .Where(d => d != null) - .Cast() - .Distinct(new DeploymentNameEqual())); - } - - private CodexDeployment? ProcessFile(string filename) - { - try - { - var lines = string.Join(" ", File.ReadAllLines(filename)); - return JsonConvert.DeserializeObject(lines); - } - catch - { - return null; - } - } - } - - internal class DeploymentNameEqual : IEqualityComparer - { - public bool Equals(CodexDeployment? x, CodexDeployment? y) - { - if (x == null && y == null) return true; - if (x == null || y == null) return false; - return x.Metadata.Name == y.Metadata.Name; - } - - public int GetHashCode([DisallowNull] CodexDeployment obj) - { - return obj.Metadata.Name.GetHashCode(); - } - } -} \ No newline at end of file diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index f027af8..f759d97 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -1,9 +1,7 @@ using ArgsUniform; using BiblioTech.Commands; -using Core; using Discord; using Discord.WebSocket; -using Logging; namespace BiblioTech { @@ -12,7 +10,6 @@ namespace BiblioTech private DiscordSocketClient client = null!; 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(); @@ -21,8 +18,6 @@ namespace BiblioTech var uniformArgs = new ArgsUniform(PrintHelp, args); Config = uniformArgs.Parse(); - DeploymentFilesMonitor.Initialize(); - EnsurePath(Config.DataPath); EnsurePath(Config.UserDataPath); EnsurePath(Config.EndpointsPath); @@ -36,26 +31,14 @@ namespace BiblioTech client = new DiscordSocketClient(); client.Log += Log; - ProjectPlugin.Load(); - ProjectPlugin.Load(); - ProjectPlugin.Load(); - - var entryPoint = new EntryPoint(new ConsoleLog(), new KubernetesWorkflow.Configuration( - kubeConfigFile: Config.KubeConfigFile, - operationTimeout: TimeSpan.FromMinutes(5), - retryDelay: TimeSpan.FromSeconds(10), - kubernetesNamespace: Config.KubeNamespace), "datafiles"); - - var ci = entryPoint.CreateInterface(); - var associateCommand = new UserAssociateCommand(); var sprCommand = new SprCommand(); var handler = new CommandHandler(client, - new GetBalanceCommand(ci, associateCommand), - new MintCommand(ci, associateCommand), + new GetBalanceCommand(associateCommand), + new MintCommand(associateCommand), sprCommand, associateCommand, - new AdminCommand(ci, sprCommand) + new AdminCommand(sprCommand) ); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index 0db6b6f..237f0a5 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -2,7 +2,6 @@ using Discord; using GethPlugin; using Newtonsoft.Json; -using Utils; namespace BiblioTech {