adds Whois command

This commit is contained in:
benbierens 2023-10-25 11:25:27 +02:00
parent bd9fc3a3cf
commit 69296577f8
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
10 changed files with 172 additions and 117 deletions

View File

@ -1,5 +1,6 @@
using Discord.WebSocket;
using BiblioTech.Options;
using Discord;
namespace BiblioTech
{
@ -45,11 +46,11 @@ namespace BiblioTech
return Program.AdminChecker.IsAdminChannel(command.Channel);
}
protected ulong GetUserId(UserOption userOption, CommandContext context)
protected IUser GetUserFromCommand(UserOption userOption, CommandContext context)
{
var targetUser = userOption.GetOptionUserId(context);
if (IsSenderAdmin(context.Command) && targetUser != null) return targetUser.Value;
return context.Command.User.Id;
var targetUser = userOption.GetUser(context);
if (IsSenderAdmin(context.Command) && targetUser != null) return targetUser;
return context.Command.User;
}
}
}

View File

@ -7,18 +7,16 @@ namespace BiblioTech
{
public abstract class BaseNetCommand : BaseCommand
{
private readonly DeploymentsFilesMonitor monitor;
private readonly CoreInterface ci;
public BaseNetCommand(DeploymentsFilesMonitor monitor, CoreInterface ci)
public BaseNetCommand(CoreInterface ci)
{
this.monitor = monitor;
this.ci = ci;
}
protected override async Task Invoke(CommandContext context)
{
var deployments = monitor.GetDeployments();
var deployments = Program.DeploymentFilesMonitor.GetDeployments();
if (deployments.Length == 0)
{
await context.Followup("No deployments are currently available.");

View File

@ -5,32 +5,25 @@ namespace BiblioTech.Commands
{
public class AdminCommand : BaseCommand
{
private readonly ClearUserAssociationCommand clearCommand;
private readonly ReportCommand reportCommand;
private readonly DeployListCommand deployListCommand;
private readonly DeployUploadCommand deployUploadCommand;
private readonly DeployRemoveCommand deployRemoveCommand;
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();
public override string Name => "admin";
public override string StartingMessage => "...";
public override string Description => "Admins only.";
public AdminCommand(DeploymentsFilesMonitor monitor)
{
clearCommand = new ClearUserAssociationCommand();
reportCommand = new ReportCommand();
deployListCommand = new DeployListCommand(monitor);
deployUploadCommand = new DeployUploadCommand(monitor);
deployRemoveCommand = new DeployRemoveCommand(monitor);
}
public override CommandOption[] Options => new CommandOption[]
{
clearCommand,
reportCommand,
deployListCommand,
deployUploadCommand,
deployRemoveCommand
deployRemoveCommand,
whoIsCommand,
};
protected override async Task Invoke(CommandContext context)
@ -52,36 +45,37 @@ namespace BiblioTech.Commands
await deployListCommand.CommandHandler(context);
await deployUploadCommand.CommandHandler(context);
await deployRemoveCommand.CommandHandler(context);
await whoIsCommand.CommandHandler(context);
}
public class ClearUserAssociationCommand : SubCommandOption
{
private readonly UserOption user = new UserOption("User to clear Eth address for.", true);
private readonly UserOption userOption = 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 };
public override CommandOption[] Options => new[] { userOption };
protected override async Task onSubCommand(CommandContext context)
{
var userId = user.GetOptionUserId(context);
if (userId == null)
var user = userOption.GetUser(context);
if (user == null)
{
await context.AdminFollowup("Failed to get user ID");
return;
}
Program.UserRepo.ClearUserAssociatedAddress(userId.Value);
Program.UserRepo.ClearUserAssociatedAddress(user);
await context.AdminFollowup("Done.");
}
}
public class ReportCommand : SubCommandOption
{
private readonly UserOption user = new UserOption(
private readonly UserOption userOption = new UserOption(
description: "User to report history for.",
isRequired: true);
@ -90,35 +84,32 @@ namespace BiblioTech.Commands
{
}
public override CommandOption[] Options => new[] { user };
public override CommandOption[] Options => new[] { userOption };
protected override async Task onSubCommand(CommandContext context)
{
var userId = user.GetOptionUserId(context);
if (userId == null)
var user = userOption.GetUser(context);
if (user == null)
{
await context.AdminFollowup("Failed to get user ID");
return;
}
var report = Program.UserRepo.GetInteractionReport(userId.Value);
var report = Program.UserRepo.GetInteractionReport(user);
await context.AdminFollowup(string.Join(Environment.NewLine, report));
}
}
public class DeployListCommand : SubCommandOption
{
private readonly DeploymentsFilesMonitor monitor;
public DeployListCommand(DeploymentsFilesMonitor monitor)
public DeployListCommand()
: base("list", "Lists current deployments.")
{
this.monitor = monitor;
}
protected override async Task onSubCommand(CommandContext context)
{
var deployments = monitor.GetDeployments();
var deployments = Program.DeploymentFilesMonitor.GetDeployments();
if (!deployments.Any())
{
@ -138,16 +129,14 @@ namespace BiblioTech.Commands
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)
public DeployUploadCommand()
: base("add", "Upload a new deployment JSON file.")
{
this.monitor = monitor;
}
public override CommandOption[] Options => new[] { fileOption };
@ -157,7 +146,7 @@ namespace BiblioTech.Commands
var file = await fileOption.Parse(context);
if (file == null) return;
var result = await monitor.DownloadDeployment(file);
var result = await Program.DeploymentFilesMonitor.DownloadDeployment(file);
if (result)
{
await context.AdminFollowup("Success!");
@ -171,16 +160,14 @@ namespace BiblioTech.Commands
public class DeployRemoveCommand : SubCommandOption
{
private readonly DeploymentsFilesMonitor monitor;
private readonly StringOption stringOption = new StringOption(
name: "name",
description: "Name of deployment to remove.",
isRequired: true);
public DeployRemoveCommand(DeploymentsFilesMonitor monitor)
public DeployRemoveCommand()
: base("remove", "Removes a deployment file.")
{
this.monitor = monitor;
}
public override CommandOption[] Options => new[] { stringOption };
@ -190,7 +177,7 @@ namespace BiblioTech.Commands
var str = await stringOption.Parse(context);
if (string.IsNullOrEmpty(str)) return;
var result = monitor.DeleteDeployment(str);
var result = Program.DeploymentFilesMonitor.DeleteDeployment(str);
if (result)
{
await context.AdminFollowup("Success!");
@ -201,5 +188,38 @@ namespace BiblioTech.Commands
}
}
}
public class WhoIsCommand : SubCommandOption
{
private readonly UserOption userOption = new UserOption("User", isRequired: false);
private readonly EthAddressOption ethAddressOption = new EthAddressOption(isRequired: false);
public WhoIsCommand()
: base(name: "whois",
description: "Fetches info about a user or ethAddress in the testnet.")
{
}
public override CommandOption[] Options => new CommandOption[]
{
userOption,
ethAddressOption
};
protected override async Task onSubCommand(CommandContext context)
{
var user = userOption.GetUser(context);
var ethAddr = await ethAddressOption.Parse(context);
if (user != null)
{
await context.AdminFollowup(Program.UserRepo.GetUserReport(user));
}
if (ethAddr != null)
{
await context.AdminFollowup(Program.UserRepo.GetUserReport(ethAddr));
}
}
}
}
}

View File

@ -12,8 +12,8 @@ namespace BiblioTech.Commands
description: "If set, get balance for another user. (Optional, admin-only)",
isRequired: false);
public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(monitor, ci)
public GetBalanceCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(ci)
{
this.userAssociateCommand = userAssociateCommand;
}
@ -25,7 +25,7 @@ namespace BiblioTech.Commands
protected override async Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts)
{
var userId = GetUserId(optionalUser, context);
var userId = GetUserFromCommand(optionalUser, context);
var addr = Program.UserRepo.GetCurrentAddressForUser(userId);
if (addr == null)
{

View File

@ -14,8 +14,8 @@ namespace BiblioTech.Commands
isRequired: false);
private readonly UserAssociateCommand userAssociateCommand;
public MintCommand(DeploymentsFilesMonitor monitor, CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(monitor, ci)
public MintCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(ci)
{
this.userAssociateCommand = userAssociateCommand;
}
@ -27,7 +27,7 @@ namespace BiblioTech.Commands
protected override async Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts)
{
var userId = GetUserId(optionalUser, context);
var userId = GetUserFromCommand(optionalUser, context);
var addr = Program.UserRepo.GetCurrentAddressForUser(userId);
if (addr == null)
{

View File

@ -4,7 +4,7 @@ namespace BiblioTech.Commands
{
public class UserAssociateCommand : BaseCommand
{
private readonly EthAddressOption ethOption = new EthAddressOption();
private readonly EthAddressOption ethOption = new EthAddressOption(isRequired: false);
private readonly UserOption optionalUser = new UserOption(
description: "If set, associates Ethereum address for another user. (Optional, admin-only)",
isRequired: false);
@ -16,11 +16,11 @@ namespace BiblioTech.Commands
protected override async Task Invoke(CommandContext context)
{
var userId = GetUserId(optionalUser, context);
var user = GetUserFromCommand(optionalUser, context);
var data = await ethOption.Parse(context);
if (data == null) return;
var currentAddress = Program.UserRepo.GetCurrentAddressForUser(userId);
var currentAddress = Program.UserRepo.GetCurrentAddressForUser(user);
if (currentAddress != null && !IsSenderAdmin(context.Command))
{
await context.Followup($"You've already set your Ethereum address to {currentAddress}.");
@ -29,7 +29,7 @@ namespace BiblioTech.Commands
// private commands
var result = Program.UserRepo.AssociateUserWithAddress(userId, data);
var result = Program.UserRepo.AssociateUserWithAddress(user, data);
if (result)
{
await context.Followup("Done! Thank you for joining the test net!");

View File

@ -5,11 +5,11 @@ namespace BiblioTech.Options
{
public class EthAddressOption : CommandOption
{
public EthAddressOption()
public EthAddressOption(bool isRequired)
: base(name: "ethaddress",
description: "Ethereum address starting with '0x'.",
type: Discord.ApplicationCommandOptionType.String,
isRequired: true)
isRequired)
{
}

View File

@ -9,13 +9,11 @@ namespace BiblioTech.Options
{
}
public ulong? GetOptionUserId(CommandContext context)
public IUser? GetUser(CommandContext context)
{
var userOptionData = context.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;
return userOptionData.Value as IUser;
}
}
}

View File

@ -45,16 +45,14 @@ namespace BiblioTech
retryDelay: TimeSpan.FromSeconds(10),
kubernetesNamespace: "not-applicable"), "datafiles");
var monitor = new DeploymentsFilesMonitor();
var ci = entryPoint.CreateInterface();
var associateCommand = new UserAssociateCommand();
var handler = new CommandHandler(client,
new GetBalanceCommand(monitor, ci, associateCommand),
new MintCommand(monitor, ci, associateCommand),
new GetBalanceCommand(ci, associateCommand),
new MintCommand(ci, associateCommand),
associateCommand,
new AdminCommand(monitor)
new AdminCommand()
);
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);

View File

@ -1,6 +1,8 @@
using CodexContractsPlugin;
using Discord;
using GethPlugin;
using Newtonsoft.Json;
using Utils;
namespace BiblioTech
{
@ -8,70 +10,62 @@ namespace BiblioTech
{
private readonly object repoLock = new object();
public bool AssociateUserWithAddress(ulong discordId, EthAddress address)
public bool AssociateUserWithAddress(IUser user, EthAddress address)
{
lock (repoLock)
{
return SetUserAddress(discordId, address);
return SetUserAddress(user, address);
}
}
public void ClearUserAssociatedAddress(ulong discordId)
public void ClearUserAssociatedAddress(IUser user)
{
lock (repoLock)
{
SetUserAddress(discordId, null);
SetUserAddress(user, null);
}
}
public void AddMintEventForUser(ulong discordId, EthAddress usedAddress, Ether eth, TestToken tokens)
public void AddMintEventForUser(IUser user, EthAddress usedAddress, Ether eth, TestToken tokens)
{
lock (repoLock)
{
var user = GetOrCreate(discordId);
user.MintEvents.Add(new UserMintEvent(DateTime.UtcNow, usedAddress, eth, tokens));
SaveUser(user);
var userData = GetOrCreate(user);
userData.MintEvents.Add(new UserMintEvent(DateTime.UtcNow, usedAddress, eth, tokens));
SaveUserData(userData);
}
}
public EthAddress? GetCurrentAddressForUser(ulong discordId)
public EthAddress? GetCurrentAddressForUser(IUser user)
{
lock (repoLock)
{
return GetOrCreate(discordId).CurrentAddress;
return GetOrCreate(user).CurrentAddress;
}
}
public string[] GetInteractionReport(ulong discordId)
public string[] GetInteractionReport(IUser user)
{
var result = new List<string>();
lock (repoLock)
{
var filename = GetFilename(discordId);
if (!File.Exists(filename))
var userData = GetUserData(user);
if (userData == null)
{
result.Add("User has not joined the test net.");
}
else
{
var user = JsonConvert.DeserializeObject<User>(File.ReadAllText(filename));
if (user == null)
result.Add("User joined on " + userData.CreatedUtc.ToString("o"));
result.Add("Current address: " + userData.CurrentAddress);
foreach (var ae in userData.AssociateEvents)
{
result.Add("Failed to load user records.");
result.Add($"{ae.Utc.ToString("o")} - Address set to: {ae.NewAddress}");
}
else
foreach (var me in userData.MintEvents)
{
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}.");
}
result.Add($"{me.Utc.ToString("o")} - Minted {me.EthReceived} and {me.TestTokensMinted} to {me.UsedAddress}.");
}
}
}
@ -79,40 +73,64 @@ namespace BiblioTech
return result.ToArray();
}
private bool SetUserAddress(ulong discordId, EthAddress? address)
public string GetUserReport(IUser user)
{
if (IsAddressUsed(address))
var userData = GetUserData(user);
if (userData == null) return "User has not joined the test net.";
return userData.CreateOverview();
}
public string GetUserReport(EthAddress ethAddress)
{
var userData = GetUserDataForAddress(ethAddress);
if (userData == null) return "No user is using this eth address.";
return userData.CreateOverview();
}
private bool SetUserAddress(IUser user, EthAddress? address)
{
if (GetUserDataForAddress(address) != null)
{
return false;
}
var user = GetOrCreate(discordId);
user.CurrentAddress = address;
user.AssociateEvents.Add(new UserAssociateAddressEvent(DateTime.UtcNow, address));
SaveUser(user);
var userData = GetOrCreate(user);
userData.CurrentAddress = address;
userData.AssociateEvents.Add(new UserAssociateAddressEvent(DateTime.UtcNow, address));
SaveUserData(userData);
return true;
}
private User GetOrCreate(ulong discordId)
private UserData? GetUserData(IUser user)
{
var filename = GetFilename(discordId);
var filename = GetFilename(user);
if (!File.Exists(filename))
{
return CreateAndSaveNewUser(discordId);
return null;
}
return JsonConvert.DeserializeObject<User>(File.ReadAllText(filename))!;
return JsonConvert.DeserializeObject<UserData>(File.ReadAllText(filename))!;
}
private User CreateAndSaveNewUser(ulong discordId)
private UserData GetOrCreate(IUser user)
{
var newUser = new User(discordId, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>());
SaveUser(newUser);
var userData = GetUserData(user);
if (userData == null)
{
return CreateAndSaveNewUserData(user);
}
return userData;
}
private UserData CreateAndSaveNewUserData(IUser user)
{
var newUser = new UserData(user.Id, user.GlobalName, DateTime.UtcNow, null, new List<UserAssociateAddressEvent>(), new List<UserMintEvent>());
SaveUserData(newUser);
return newUser;
}
private bool IsAddressUsed(EthAddress? address)
private UserData? GetUserDataForAddress(EthAddress? address)
{
if (address == null) return false;
if (address == null) return null;
// If this becomes a performance problem, switch to in-memory cached list.
var files = Directory.GetFiles(Program.Config.UserDataPath);
@ -120,24 +138,34 @@ namespace BiblioTech
{
try
{
var user = JsonConvert.DeserializeObject<User>(File.ReadAllText(file))!;
var user = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
if (user.CurrentAddress != null &&
user.CurrentAddress.Address == address.Address)
{
return true;
return user;
}
}
catch { }
}
return false;
return null;
}
private void SaveUser(User user)
private void SaveUserData(UserData userData)
{
var filename = GetFilename(user.DiscordId);
var filename = GetFilename(userData);
if (File.Exists(filename)) File.Delete(filename);
File.WriteAllText(filename, JsonConvert.SerializeObject(user));
File.WriteAllText(filename, JsonConvert.SerializeObject(userData));
}
private static string GetFilename(IUser user)
{
return GetFilename(user.Id);
}
private static string GetFilename(UserData userData)
{
return GetFilename(userData.DiscordId);
}
private static string GetFilename(ulong discordId)
@ -146,11 +174,12 @@ namespace BiblioTech
}
}
public class User
public class UserData
{
public User(ulong discordId, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents)
public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List<UserAssociateAddressEvent> associateEvents, List<UserMintEvent> mintEvents)
{
DiscordId = discordId;
Name = name;
CreatedUtc = createdUtc;
CurrentAddress = currentAddress;
AssociateEvents = associateEvents;
@ -158,10 +187,21 @@ namespace BiblioTech
}
public ulong DiscordId { get; }
public string Name { get; }
public DateTime CreatedUtc { get; }
public EthAddress? CurrentAddress { get; set; }
public List<UserAssociateAddressEvent> AssociateEvents { get; }
public List<UserMintEvent> MintEvents { get; }
public string CreateOverview()
{
var nl = Environment.NewLine;
return
$"name: '{Name}' - id:{DiscordId}{nl}" +
$"joined: {CreatedUtc.ToString("o")}{nl}" +
$"current address: {CurrentAddress}{nl}" +
$"{AssociateEvents.Count + MintEvents.Count} total bot events.";
}
}
public class UserAssociateAddressEvent