2
0
mirror of synced 2025-02-23 05:28:17 +00:00

cleanup of admin commands

This commit is contained in:
benbierens 2023-10-24 15:25:45 +02:00
parent 5aff8c6f6d
commit a0461a446e
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
16 changed files with 297 additions and 211 deletions

View File

@ -1,6 +1,5 @@
using Discord.WebSocket; using Discord.WebSocket;
using Discord; using BiblioTech.Options;
using BiblioTech.Commands;
namespace BiblioTech namespace BiblioTech
{ {
@ -24,7 +23,7 @@ namespace BiblioTech
try try
{ {
await command.RespondAsync(StartingMessage); await command.RespondAsync(StartingMessage);
await Invoke(command); await Invoke(new CommandContext(command, command.Data.Options));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -33,43 +32,18 @@ namespace BiblioTech
} }
} }
protected abstract Task Invoke(SocketSlashCommand command); protected abstract Task Invoke(CommandContext context);
protected bool IsSenderAdmin(SocketSlashCommand command) protected bool IsSenderAdmin(SocketSlashCommand command)
{ {
return Program.AdminChecker.IsUserAdmin(command.User.Id); return Program.AdminChecker.IsUserAdmin(command.User.Id);
} }
protected ulong GetUserId(UserOption userOption, SocketSlashCommand command) protected ulong GetUserId(UserOption userOption, CommandContext context)
{ {
var targetUser = userOption.GetOptionUserId(command); var targetUser = userOption.GetOptionUserId(context);
if (IsSenderAdmin(command) && targetUser != null) return targetUser.Value; if (IsSenderAdmin(context.Command) && targetUser != null) return targetUser.Value;
return command.User.Id; return context.Command.User.Id;
}
}
public class CommandOption
{
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; }
public virtual SlashCommandOptionBuilder Build()
{
return new SlashCommandOptionBuilder()
.WithName(Name)
.WithDescription(Description)
.WithType(Type)
.WithRequired(IsRequired);
} }
} }
} }

View File

@ -1,6 +1,6 @@
using CodexContractsPlugin; using BiblioTech.Options;
using CodexContractsPlugin;
using Core; using Core;
using Discord.WebSocket;
using GethPlugin; using GethPlugin;
namespace BiblioTech namespace BiblioTech
@ -16,17 +16,17 @@ namespace BiblioTech
this.ci = ci; this.ci = ci;
} }
protected override async Task Invoke(SocketSlashCommand command) protected override async Task Invoke(CommandContext context)
{ {
var deployments = monitor.GetDeployments(); var deployments = monitor.GetDeployments();
if (deployments.Length == 0) if (deployments.Length == 0)
{ {
await command.FollowupAsync("No deployments are currently available."); await context.Command.FollowupAsync("No deployments are currently available.");
return; return;
} }
if (deployments.Length > 1) if (deployments.Length > 1)
{ {
await command.FollowupAsync("Multiple deployments are online. I don't know which one to pick!"); await context.Command.FollowupAsync("Multiple deployments are online. I don't know which one to pick!");
return; return;
} }
@ -37,9 +37,9 @@ namespace BiblioTech
var gethNode = ci.WrapGethDeployment(gethDeployment); var gethNode = ci.WrapGethDeployment(gethDeployment);
var contracts = ci.WrapCodexContractsDeployment(contractsDeployment); var contracts = ci.WrapCodexContractsDeployment(contractsDeployment);
await Execute(command, gethNode, contracts); await Execute(context, gethNode, contracts);
} }
protected abstract Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts); protected abstract Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts);
} }
} }

View File

@ -1,46 +1,156 @@
using Discord; using BiblioTech.Options;
using Discord.WebSocket; using CodexPlugin;
namespace BiblioTech.Commands namespace BiblioTech.Commands
{ {
public class SubCommandOption : CommandOption
{
private readonly CommandOption[] options;
public SubCommandOption(string name, string description, params CommandOption[] options)
: base(name, description, type: ApplicationCommandOptionType.SubCommand, isRequired: false)
{
this.options = options;
}
public override SlashCommandOptionBuilder Build()
{
var builder = base.Build();
foreach (var option in options)
{
builder.AddOption(option.Build());
}
return builder;
}
}
public class AdminCommand : BaseCommand public class AdminCommand : BaseCommand
{ {
private readonly ClearUserAssociationCommand clearCommand;
private readonly ReportCommand reportCommand;
private readonly DeployListCommand deployListCommand;
private readonly DeployUploadCommand deployUploadCommand;
public override string Name => "admin"; public override string Name => "admin";
public override string StartingMessage => "..."; public override string StartingMessage => "...";
public override string Description => "Admins only."; public override string Description => "Admins only.";
private readonly SubCommandOption aaa = new SubCommandOption("aaa", "does AAA", new EthAddressOption()); public AdminCommand(DeploymentsFilesMonitor monitor)
private readonly SubCommandOption bbb = new SubCommandOption("bbb", "does BBB", new UserOption("a user", true)); {
clearCommand = new ClearUserAssociationCommand();
reportCommand = new ReportCommand();
deployListCommand = new DeployListCommand(monitor);
deployUploadCommand = new DeployUploadCommand(monitor);
}
public override CommandOption[] Options => new CommandOption[] public override CommandOption[] Options => new CommandOption[]
{ {
aaa, bbb clearCommand,
reportCommand,
deployListCommand,
deployUploadCommand,
}; };
protected override Task Invoke(SocketSlashCommand command) protected override async Task Invoke(CommandContext context)
{ {
return Task.CompletedTask; if (!IsSenderAdmin(context.Command))
{
await context.Command.FollowupAsync("You're not an admin.");
return;
}
await clearCommand.CommandHandler(context);
await reportCommand.CommandHandler(context);
await deployListCommand.CommandHandler(context);
await deployUploadCommand.CommandHandler(context);
}
public class ClearUserAssociationCommand : SubCommandOption
{
private readonly UserOption user = new UserOption("User to clear Eth address for.", true);
public ClearUserAssociationCommand()
: base("clear", "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 onSubCommand(CommandContext context)
{
var userId = user.GetOptionUserId(context);
if (userId == null)
{
await context.Command.FollowupAsync("Failed to get user ID");
return;
}
Program.UserRepo.ClearUserAssociatedAddress(userId.Value);
await context.Command.FollowupAsync("Done.");
}
}
public class ReportCommand : SubCommandOption
{
private readonly UserOption user = new UserOption(
description: "User to report history for.",
isRequired: true);
public ReportCommand()
: base("report", "Admin only. Reports bot-interaction history for a user.")
{
}
public override CommandOption[] Options => new[] { user };
protected override async Task onSubCommand(CommandContext context)
{
var userId = user.GetOptionUserId(context);
if (userId == null)
{
await context.Command.FollowupAsync("Failed to get user ID");
return;
}
var report = Program.UserRepo.GetInteractionReport(userId.Value);
await context.Command.FollowupAsync(string.Join(Environment.NewLine, report));
}
}
public class DeployListCommand : SubCommandOption
{
private readonly DeploymentsFilesMonitor monitor;
public DeployListCommand(DeploymentsFilesMonitor monitor)
: base("list", "Lists current deployments.")
{
this.monitor = monitor;
}
protected override async Task onSubCommand(CommandContext context)
{
var deployments = monitor.GetDeployments();
if (!deployments.Any())
{
await context.Command.FollowupAsync("No deployments available.");
return;
}
await context.Command.FollowupAsync($"Deployments: {string.Join(", ", deployments.Select(FormatDeployment))}");
}
private string FormatDeployment(CodexDeployment deployment)
{
var m = deployment.Metadata;
return $"{m.Name} ({m.StartUtc.ToString("o")})";
}
}
public class DeployUploadCommand : SubCommandOption
{
private readonly DeploymentsFilesMonitor monitor;
private readonly FileAttachementOption fileOption = new FileAttachementOption(
name: "json",
description: "Codex-deployment json to add.",
isRequired: true);
public DeployUploadCommand(DeploymentsFilesMonitor monitor)
: base("add", "Upload a new deployment JSON file.")
{
this.monitor = monitor;
}
public override CommandOption[] Options => new[] { fileOption };
protected override async Task onSubCommand(CommandContext context)
{
var file = await fileOption.Parse(context);
if (file == null) return;
await context.Command.FollowupAsync("Received: " + file.Size);
// todo pass to monitor, add to folder.
}
} }
} }
} }

View File

@ -1,35 +0,0 @@
using Discord.WebSocket;
namespace BiblioTech.Commands
{
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.";
public override CommandOption[] Options => new[] { 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;
}
Program.UserRepo.ClearUserAssociatedAddress(userId.Value);
await command.FollowupAsync("Done."); ;
}
}
}

View File

@ -1,38 +0,0 @@
using CodexPlugin;
using Discord.WebSocket;
namespace BiblioTech.Commands
{
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 active TestNet 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(FormatDeployment))}");
}
private string FormatDeployment(CodexDeployment deployment)
{
var m = deployment.Metadata;
return $"{m.Name} ({m.StartUtc.ToString("o")})";
}
}
}

View File

@ -1,6 +1,6 @@
using CodexContractsPlugin; using BiblioTech.Options;
using CodexContractsPlugin;
using Core; using Core;
using Discord.WebSocket;
using GethPlugin; using GethPlugin;
namespace BiblioTech.Commands namespace BiblioTech.Commands
@ -23,20 +23,20 @@ namespace BiblioTech.Commands
public override string Description => "Shows Eth and TestToken balance of an eth address."; public override string Description => "Shows Eth and TestToken balance of an eth address.";
public override CommandOption[] Options => new[] { optionalUser }; public override CommandOption[] Options => new[] { optionalUser };
protected override async Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts) protected override async Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts)
{ {
var userId = GetUserId(optionalUser, command); var userId = GetUserId(optionalUser, context);
var addr = Program.UserRepo.GetCurrentAddressForUser(userId); var addr = Program.UserRepo.GetCurrentAddressForUser(userId);
if (addr == null) if (addr == null)
{ {
await command.FollowupAsync($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first."); await context.Command.FollowupAsync($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first.");
return; return;
} }
var eth = gethNode.GetEthBalance(addr); var eth = gethNode.GetEthBalance(addr);
var testTokens = contracts.GetTestTokenBalance(gethNode, addr); var testTokens = contracts.GetTestTokenBalance(gethNode, addr);
await command.FollowupAsync($"{command.User.Username} has {eth} and {testTokens}."); await context.Command.FollowupAsync($"{context.Command.User.Username} has {eth} and {testTokens}.");
} }
} }
} }

View File

@ -1,6 +1,6 @@
using CodexContractsPlugin; using BiblioTech.Options;
using CodexContractsPlugin;
using Core; using Core;
using Discord.WebSocket;
using GethPlugin; using GethPlugin;
namespace BiblioTech.Commands namespace BiblioTech.Commands
@ -25,13 +25,13 @@ namespace BiblioTech.Commands
public override string Description => "Mint some TestTokens and send some Eth to the user if their balance is low."; 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 }; public override CommandOption[] Options => new[] { optionalUser };
protected override async Task Execute(SocketSlashCommand command, IGethNode gethNode, ICodexContracts contracts) protected override async Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts)
{ {
var userId = GetUserId(optionalUser, command); var userId = GetUserId(optionalUser, context);
var addr = Program.UserRepo.GetCurrentAddressForUser(userId); var addr = Program.UserRepo.GetCurrentAddressForUser(userId);
if (addr == null) if (addr == null)
{ {
await command.FollowupAsync($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first."); await context.Command.FollowupAsync($"No address has been set for this user. Please use '/{userAssociateCommand.Name}' to set it first.");
return; return;
} }
@ -42,7 +42,7 @@ namespace BiblioTech.Commands
Program.UserRepo.AddMintEventForUser(userId, addr, sentEth, mintedTokens); Program.UserRepo.AddMintEventForUser(userId, addr, sentEth, mintedTokens);
await command.FollowupAsync(string.Join(Environment.NewLine, report)); await context.Command.FollowupAsync(string.Join(Environment.NewLine, report));
} }
private TestToken ProcessTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr, List<string> report) private TestToken ProcessTokens(IGethNode gethNode, ICodexContracts contracts, EthAddress addr, List<string> report)

View File

@ -1,35 +0,0 @@
using Discord.WebSocket;
namespace BiblioTech.Commands
{
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.";
public override CommandOption[] Options => new[] { 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));
}
}
}

View File

@ -1,4 +1,4 @@
using Discord.WebSocket; using BiblioTech.Options;
namespace BiblioTech.Commands namespace BiblioTech.Commands
{ {
@ -14,21 +14,21 @@ namespace BiblioTech.Commands
public override string Description => "Associates a Discord user with an Ethereum address."; public override string Description => "Associates a Discord user with an Ethereum address.";
public override CommandOption[] Options => new CommandOption[] { ethOption, optionalUser }; public override CommandOption[] Options => new CommandOption[] { ethOption, optionalUser };
protected override async Task Invoke(SocketSlashCommand command) protected override async Task Invoke(CommandContext context)
{ {
var userId = GetUserId(optionalUser, command); var userId = GetUserId(optionalUser, context);
var data = await ethOption.Parse(command); var data = await ethOption.Parse(context);
if (data == null) return; if (data == null) return;
var currentAddress = Program.UserRepo.GetCurrentAddressForUser(userId); var currentAddress = Program.UserRepo.GetCurrentAddressForUser(userId);
if (currentAddress != null && !IsSenderAdmin(command)) if (currentAddress != null && !IsSenderAdmin(context.Command))
{ {
await command.FollowupAsync($"You've already set your Ethereum address to {currentAddress}."); await context.Command.FollowupAsync($"You've already set your Ethereum address to {currentAddress}.");
return; return;
} }
Program.UserRepo.AssociateUserWithAddress(userId, data); Program.UserRepo.AssociateUserWithAddress(userId, data);
await command.FollowupAsync("Done! Thank you for joining the test net!"); await context.Command.FollowupAsync("Done! Thank you for joining the test net!");
} }
} }
} }

View File

@ -0,0 +1,16 @@
using Discord.WebSocket;
namespace BiblioTech.Options
{
public class CommandContext
{
public CommandContext(SocketSlashCommand command, IReadOnlyCollection<SocketSlashCommandDataOption> options)
{
Command = command;
Options = options;
}
public SocketSlashCommand Command { get; }
public IReadOnlyCollection<SocketSlashCommandDataOption> Options { get; }
}
}

View File

@ -0,0 +1,29 @@
using Discord;
namespace BiblioTech.Options
{
public abstract class CommandOption
{
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; }
public virtual SlashCommandOptionBuilder Build()
{
return new SlashCommandOptionBuilder()
.WithName(Name)
.WithDescription(Description)
.WithType(Type)
.WithRequired(IsRequired);
}
}
}

View File

@ -1,8 +1,7 @@
using Discord.WebSocket; using GethPlugin;
using GethPlugin;
using Nethereum.Util; using Nethereum.Util;
namespace BiblioTech.Commands namespace BiblioTech.Options
{ {
public class EthAddressOption : CommandOption public class EthAddressOption : CommandOption
{ {
@ -14,18 +13,18 @@ namespace BiblioTech.Commands
{ {
} }
public async Task<EthAddress?> Parse(SocketSlashCommand command) public async Task<EthAddress?> Parse(CommandContext context)
{ {
var ethOptionData = command.Data.Options.SingleOrDefault(o => o.Name == Name); var ethOptionData = context.Options.SingleOrDefault(o => o.Name == Name);
if (ethOptionData == null) if (ethOptionData == null)
{ {
await command.FollowupAsync("EthAddress option not received."); await context.Command.FollowupAsync("EthAddress option not received.");
return null; return null;
} }
var ethAddressStr = ethOptionData.Value as string; var ethAddressStr = ethOptionData.Value as string;
if (string.IsNullOrEmpty(ethAddressStr)) if (string.IsNullOrEmpty(ethAddressStr))
{ {
await command.FollowupAsync("EthAddress is null or empty."); await context.Command.FollowupAsync("EthAddress is null or empty.");
return null; return null;
} }
@ -33,7 +32,7 @@ namespace BiblioTech.Commands
!AddressUtil.Current.IsValidEthereumAddressHexFormat(ethAddressStr) || !AddressUtil.Current.IsValidEthereumAddressHexFormat(ethAddressStr) ||
!AddressUtil.Current.IsChecksumAddress(ethAddressStr)) !AddressUtil.Current.IsChecksumAddress(ethAddressStr))
{ {
await command.FollowupAsync("EthAddress is not valid."); await context.Command.FollowupAsync("EthAddress is not valid.");
return null; return null;
} }

View File

@ -0,0 +1,30 @@
using Discord;
namespace BiblioTech.Options
{
public class FileAttachementOption : CommandOption
{
public FileAttachementOption(string name, string description, bool isRequired)
: base(name, description, type: ApplicationCommandOptionType.Attachment, isRequired)
{
}
public async Task<IAttachment?> Parse(CommandContext context)
{
var fileOptionData = context.Options.SingleOrDefault(o => o.Name == Name);
if (fileOptionData == null)
{
await context.Command.FollowupAsync("Attachement option not received.");
return null;
}
var attachement = fileOptionData.Value as IAttachment;
if (attachement == null)
{
await context.Command.FollowupAsync("Attachement is null or empty.");
return null;
}
return attachement;
}
}
}

View File

@ -0,0 +1,40 @@
using Discord;
namespace BiblioTech.Options
{
public abstract class SubCommandOption : CommandOption
{
public SubCommandOption(string name, string description)
: base(name, description, type: ApplicationCommandOptionType.SubCommand, isRequired: false)
{
}
public override SlashCommandOptionBuilder Build()
{
var builder = base.Build();
foreach (var option in Options)
{
builder.AddOption(option.Build());
}
return builder;
}
public async Task CommandHandler(CommandContext context)
{
var mine = context.Options.SingleOrDefault(o => o.Name == Name);
if (mine == null) return;
await onSubCommand(new CommandContext(context.Command, mine.Options));
}
public virtual CommandOption[] Options
{
get
{
return Array.Empty<CommandOption>();
}
}
protected abstract Task onSubCommand(CommandContext context);
}
}

View File

@ -1,7 +1,6 @@
using Discord; using Discord;
using Discord.WebSocket;
namespace BiblioTech.Commands namespace BiblioTech.Options
{ {
public class UserOption : CommandOption public class UserOption : CommandOption
{ {
@ -10,9 +9,9 @@ namespace BiblioTech.Commands
{ {
} }
public ulong? GetOptionUserId(SocketSlashCommand command) public ulong? GetOptionUserId(CommandContext context)
{ {
var userOptionData = command.Data.Options.SingleOrDefault(o => o.Name == Name); var userOptionData = context.Options.SingleOrDefault(o => o.Name == Name);
if (userOptionData == null) return null; if (userOptionData == null) return null;
var user = userOptionData.Value as IUser; var user = userOptionData.Value as IUser;
if (user == null) return null; if (user == null) return null;

View File

@ -51,13 +51,10 @@ namespace BiblioTech
var associateCommand = new UserAssociateCommand(); var associateCommand = new UserAssociateCommand();
var handler = new CommandHandler(client, var handler = new CommandHandler(client,
//new ClearUserAssociationCommand(),
new GetBalanceCommand(monitor, ci, associateCommand), new GetBalanceCommand(monitor, ci, associateCommand),
new MintCommand(monitor, ci, associateCommand), new MintCommand(monitor, ci, associateCommand),
//new ReportHistoryCommand(),
associateCommand, associateCommand,
//new DeploymentsCommand(monitor), new AdminCommand(monitor)
new AdminCommand()
); );
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);