2
0
mirror of synced 2025-01-11 17:14:25 +00:00

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 Discord.WebSocket;
using BiblioTech.Options; using BiblioTech.Options;
using Discord;
namespace BiblioTech namespace BiblioTech
{ {
@ -45,11 +46,11 @@ namespace BiblioTech
return Program.AdminChecker.IsAdminChannel(command.Channel); 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); var targetUser = userOption.GetUser(context);
if (IsSenderAdmin(context.Command) && targetUser != null) return targetUser.Value; if (IsSenderAdmin(context.Command) && targetUser != null) return targetUser;
return context.Command.User.Id; return context.Command.User;
} }
} }
} }

View File

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

View File

@ -5,32 +5,25 @@ namespace BiblioTech.Commands
{ {
public class AdminCommand : BaseCommand public class AdminCommand : BaseCommand
{ {
private readonly ClearUserAssociationCommand clearCommand; private readonly ClearUserAssociationCommand clearCommand = new ClearUserAssociationCommand();
private readonly ReportCommand reportCommand; private readonly ReportCommand reportCommand = new ReportCommand();
private readonly DeployListCommand deployListCommand; private readonly DeployListCommand deployListCommand = new DeployListCommand();
private readonly DeployUploadCommand deployUploadCommand; private readonly DeployUploadCommand deployUploadCommand = new DeployUploadCommand();
private readonly DeployRemoveCommand deployRemoveCommand; private readonly DeployRemoveCommand deployRemoveCommand = new DeployRemoveCommand();
private readonly WhoIsCommand whoIsCommand = new WhoIsCommand();
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.";
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[] public override CommandOption[] Options => new CommandOption[]
{ {
clearCommand, clearCommand,
reportCommand, reportCommand,
deployListCommand, deployListCommand,
deployUploadCommand, deployUploadCommand,
deployRemoveCommand deployRemoveCommand,
whoIsCommand,
}; };
protected override async Task Invoke(CommandContext context) protected override async Task Invoke(CommandContext context)
@ -52,36 +45,37 @@ namespace BiblioTech.Commands
await deployListCommand.CommandHandler(context); await deployListCommand.CommandHandler(context);
await deployUploadCommand.CommandHandler(context); await deployUploadCommand.CommandHandler(context);
await deployRemoveCommand.CommandHandler(context); await deployRemoveCommand.CommandHandler(context);
await whoIsCommand.CommandHandler(context);
} }
public class ClearUserAssociationCommand : SubCommandOption 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() public ClearUserAssociationCommand()
: base("clear", "Admin only. Clears current Eth address for a user, allowing them to set a new one.") : 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) protected override async Task onSubCommand(CommandContext context)
{ {
var userId = user.GetOptionUserId(context); var user = userOption.GetUser(context);
if (userId == null) if (user == null)
{ {
await context.AdminFollowup("Failed to get user ID"); await context.AdminFollowup("Failed to get user ID");
return; return;
} }
Program.UserRepo.ClearUserAssociatedAddress(userId.Value); Program.UserRepo.ClearUserAssociatedAddress(user);
await context.AdminFollowup("Done."); await context.AdminFollowup("Done.");
} }
} }
public class ReportCommand : SubCommandOption public class ReportCommand : SubCommandOption
{ {
private readonly UserOption user = new UserOption( private readonly UserOption userOption = new UserOption(
description: "User to report history for.", description: "User to report history for.",
isRequired: true); 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) protected override async Task onSubCommand(CommandContext context)
{ {
var userId = user.GetOptionUserId(context); var user = userOption.GetUser(context);
if (userId == null) if (user == null)
{ {
await context.AdminFollowup("Failed to get user ID"); await context.AdminFollowup("Failed to get user ID");
return; return;
} }
var report = Program.UserRepo.GetInteractionReport(userId.Value); var report = Program.UserRepo.GetInteractionReport(user);
await context.AdminFollowup(string.Join(Environment.NewLine, report)); await context.AdminFollowup(string.Join(Environment.NewLine, report));
} }
} }
public class DeployListCommand : SubCommandOption public class DeployListCommand : SubCommandOption
{ {
private readonly DeploymentsFilesMonitor monitor; public DeployListCommand()
public DeployListCommand(DeploymentsFilesMonitor monitor)
: base("list", "Lists current deployments.") : base("list", "Lists current deployments.")
{ {
this.monitor = monitor;
} }
protected override async Task onSubCommand(CommandContext context) protected override async Task onSubCommand(CommandContext context)
{ {
var deployments = monitor.GetDeployments(); var deployments = Program.DeploymentFilesMonitor.GetDeployments();
if (!deployments.Any()) if (!deployments.Any())
{ {
@ -138,16 +129,14 @@ namespace BiblioTech.Commands
public class DeployUploadCommand : SubCommandOption public class DeployUploadCommand : SubCommandOption
{ {
private readonly DeploymentsFilesMonitor monitor;
private readonly FileAttachementOption fileOption = new FileAttachementOption( private readonly FileAttachementOption fileOption = new FileAttachementOption(
name: "json", name: "json",
description: "Codex-deployment json to add.", description: "Codex-deployment json to add.",
isRequired: true); isRequired: true);
public DeployUploadCommand(DeploymentsFilesMonitor monitor) public DeployUploadCommand()
: base("add", "Upload a new deployment JSON file.") : base("add", "Upload a new deployment JSON file.")
{ {
this.monitor = monitor;
} }
public override CommandOption[] Options => new[] { fileOption }; public override CommandOption[] Options => new[] { fileOption };
@ -157,7 +146,7 @@ namespace BiblioTech.Commands
var file = await fileOption.Parse(context); var file = await fileOption.Parse(context);
if (file == null) return; if (file == null) return;
var result = await monitor.DownloadDeployment(file); var result = await Program.DeploymentFilesMonitor.DownloadDeployment(file);
if (result) if (result)
{ {
await context.AdminFollowup("Success!"); await context.AdminFollowup("Success!");
@ -171,16 +160,14 @@ namespace BiblioTech.Commands
public class DeployRemoveCommand : SubCommandOption public class DeployRemoveCommand : SubCommandOption
{ {
private readonly DeploymentsFilesMonitor monitor;
private readonly StringOption stringOption = new StringOption( private readonly StringOption stringOption = new StringOption(
name: "name", name: "name",
description: "Name of deployment to remove.", description: "Name of deployment to remove.",
isRequired: true); isRequired: true);
public DeployRemoveCommand(DeploymentsFilesMonitor monitor) public DeployRemoveCommand()
: base("remove", "Removes a deployment file.") : base("remove", "Removes a deployment file.")
{ {
this.monitor = monitor;
} }
public override CommandOption[] Options => new[] { stringOption }; public override CommandOption[] Options => new[] { stringOption };
@ -190,7 +177,7 @@ namespace BiblioTech.Commands
var str = await stringOption.Parse(context); var str = await stringOption.Parse(context);
if (string.IsNullOrEmpty(str)) return; if (string.IsNullOrEmpty(str)) return;
var result = monitor.DeleteDeployment(str); var result = Program.DeploymentFilesMonitor.DeleteDeployment(str);
if (result) if (result)
{ {
await context.AdminFollowup("Success!"); 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)", description: "If set, get balance for another user. (Optional, admin-only)",
isRequired: false); isRequired: false);
public GetBalanceCommand(DeploymentsFilesMonitor monitor, CoreInterface ci, UserAssociateCommand userAssociateCommand) public GetBalanceCommand(CoreInterface ci, UserAssociateCommand userAssociateCommand)
: base(monitor, ci) : base(ci)
{ {
this.userAssociateCommand = userAssociateCommand; this.userAssociateCommand = userAssociateCommand;
} }
@ -25,7 +25,7 @@ namespace BiblioTech.Commands
protected override async Task Execute(CommandContext context, IGethNode gethNode, ICodexContracts contracts) 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); var addr = Program.UserRepo.GetCurrentAddressForUser(userId);
if (addr == null) if (addr == null)
{ {

View File

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

View File

@ -4,7 +4,7 @@ namespace BiblioTech.Commands
{ {
public class UserAssociateCommand : BaseCommand public class UserAssociateCommand : BaseCommand
{ {
private readonly EthAddressOption ethOption = new EthAddressOption(); private readonly EthAddressOption ethOption = new EthAddressOption(isRequired: false);
private readonly UserOption optionalUser = new UserOption( private readonly UserOption optionalUser = new UserOption(
description: "If set, associates Ethereum address for another user. (Optional, admin-only)", description: "If set, associates Ethereum address for another user. (Optional, admin-only)",
isRequired: false); isRequired: false);
@ -16,11 +16,11 @@ namespace BiblioTech.Commands
protected override async Task Invoke(CommandContext context) protected override async Task Invoke(CommandContext context)
{ {
var userId = GetUserId(optionalUser, context); var user = GetUserFromCommand(optionalUser, context);
var data = await ethOption.Parse(context); 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(user);
if (currentAddress != null && !IsSenderAdmin(context.Command)) if (currentAddress != null && !IsSenderAdmin(context.Command))
{ {
await context.Followup($"You've already set your Ethereum address to {currentAddress}."); await context.Followup($"You've already set your Ethereum address to {currentAddress}.");
@ -29,7 +29,7 @@ namespace BiblioTech.Commands
// private commands // private commands
var result = Program.UserRepo.AssociateUserWithAddress(userId, data); var result = Program.UserRepo.AssociateUserWithAddress(user, data);
if (result) if (result)
{ {
await context.Followup("Done! Thank you for joining the test net!"); 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 class EthAddressOption : CommandOption
{ {
public EthAddressOption() public EthAddressOption(bool isRequired)
: base(name: "ethaddress", : base(name: "ethaddress",
description: "Ethereum address starting with '0x'.", description: "Ethereum address starting with '0x'.",
type: Discord.ApplicationCommandOptionType.String, 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); 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; return userOptionData.Value as IUser;
if (user == null) return null;
return user.Id;
} }
} }
} }

View File

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

View File

@ -1,6 +1,8 @@
using CodexContractsPlugin; using CodexContractsPlugin;
using Discord;
using GethPlugin; using GethPlugin;
using Newtonsoft.Json; using Newtonsoft.Json;
using Utils;
namespace BiblioTech namespace BiblioTech
{ {
@ -8,70 +10,62 @@ namespace BiblioTech
{ {
private readonly object repoLock = new object(); private readonly object repoLock = new object();
public bool AssociateUserWithAddress(ulong discordId, EthAddress address) public bool AssociateUserWithAddress(IUser user, EthAddress address)
{ {
lock (repoLock) lock (repoLock)
{ {
return SetUserAddress(discordId, address); return SetUserAddress(user, address);
} }
} }
public void ClearUserAssociatedAddress(ulong discordId) public void ClearUserAssociatedAddress(IUser user)
{ {
lock (repoLock) 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) lock (repoLock)
{ {
var user = GetOrCreate(discordId); var userData = GetOrCreate(user);
user.MintEvents.Add(new UserMintEvent(DateTime.UtcNow, usedAddress, eth, tokens)); userData.MintEvents.Add(new UserMintEvent(DateTime.UtcNow, usedAddress, eth, tokens));
SaveUser(user); SaveUserData(userData);
} }
} }
public EthAddress? GetCurrentAddressForUser(ulong discordId) public EthAddress? GetCurrentAddressForUser(IUser user)
{ {
lock (repoLock) 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>(); var result = new List<string>();
lock (repoLock) lock (repoLock)
{ {
var filename = GetFilename(discordId); var userData = GetUserData(user);
if (!File.Exists(filename)) if (userData == null)
{ {
result.Add("User has not joined the test net."); result.Add("User has not joined the test net.");
} }
else else
{ {
var user = JsonConvert.DeserializeObject<User>(File.ReadAllText(filename)); result.Add("User joined on " + userData.CreatedUtc.ToString("o"));
if (user == null) 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($"{me.Utc.ToString("o")} - Minted {me.EthReceived} and {me.TestTokensMinted} to {me.UsedAddress}.");
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}.");
}
} }
} }
} }
@ -79,40 +73,64 @@ namespace BiblioTech
return result.ToArray(); 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; return false;
} }
var user = GetOrCreate(discordId); var userData = GetOrCreate(user);
user.CurrentAddress = address; userData.CurrentAddress = address;
user.AssociateEvents.Add(new UserAssociateAddressEvent(DateTime.UtcNow, address)); userData.AssociateEvents.Add(new UserAssociateAddressEvent(DateTime.UtcNow, address));
SaveUser(user); SaveUserData(userData);
return true; return true;
} }
private User GetOrCreate(ulong discordId) private UserData? GetUserData(IUser user)
{ {
var filename = GetFilename(discordId); var filename = GetFilename(user);
if (!File.Exists(filename)) 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>()); var userData = GetUserData(user);
SaveUser(newUser); 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; 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. // If this becomes a performance problem, switch to in-memory cached list.
var files = Directory.GetFiles(Program.Config.UserDataPath); var files = Directory.GetFiles(Program.Config.UserDataPath);
@ -120,24 +138,34 @@ namespace BiblioTech
{ {
try try
{ {
var user = JsonConvert.DeserializeObject<User>(File.ReadAllText(file))!; var user = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(file))!;
if (user.CurrentAddress != null && if (user.CurrentAddress != null &&
user.CurrentAddress.Address == address.Address) user.CurrentAddress.Address == address.Address)
{ {
return true; return user;
} }
} }
catch { } 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); 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) 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; DiscordId = discordId;
Name = name;
CreatedUtc = createdUtc; CreatedUtc = createdUtc;
CurrentAddress = currentAddress; CurrentAddress = currentAddress;
AssociateEvents = associateEvents; AssociateEvents = associateEvents;
@ -158,10 +187,21 @@ namespace BiblioTech
} }
public ulong DiscordId { get; } public ulong DiscordId { get; }
public string Name { get; }
public DateTime CreatedUtc { get; } public DateTime CreatedUtc { get; }
public EthAddress? CurrentAddress { get; set; } public EthAddress? CurrentAddress { get; set; }
public List<UserAssociateAddressEvent> AssociateEvents { get; } public List<UserAssociateAddressEvent> AssociateEvents { get; }
public List<UserMintEvent> MintEvents { 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 public class UserAssociateAddressEvent