Merge branch 'feature/bot-upgrade'

This commit is contained in:
benbierens 2023-12-18 11:28:22 +01:00
commit 2c026f99ca
No known key found for this signature in database
GPG Key ID: FE44815D96D0A1AA
15 changed files with 137 additions and 59 deletions

View File

@ -0,0 +1,17 @@
namespace Logging
{
public class FileLog : BaseLog
{
public FileLog(string fullFilename)
{
FullFilename = fullFilename;
}
public string FullFilename { get; }
protected override string GetFullName()
{
return FullFilename;
}
}
}

View File

@ -17,11 +17,12 @@ namespace NethereumWorkflow
this.web3 = web3; this.web3 = web3;
} }
public void SendEth(string toAddress, decimal ethAmount) public string SendEth(string toAddress, decimal ethAmount)
{ {
log.Debug(); log.Debug();
var receipt = Time.Wait(web3.Eth.GetEtherTransferService().TransferEtherAndWaitForReceiptAsync(toAddress, ethAmount)); var receipt = Time.Wait(web3.Eth.GetEtherTransferService().TransferEtherAndWaitForReceiptAsync(toAddress, ethAmount));
if (!receipt.Succeeded()) throw new Exception("Unable to send Eth"); if (!receipt.Succeeded()) throw new Exception("Unable to send Eth");
return receipt.TransactionHash;
} }
public decimal GetEthBalance() public decimal GetEthBalance()
@ -44,12 +45,13 @@ namespace NethereumWorkflow
return Time.Wait(handler.QueryAsync<TResult>(contractAddress, function)); return Time.Wait(handler.QueryAsync<TResult>(contractAddress, function));
} }
public void SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new() public string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{ {
log.Debug(); log.Debug();
var handler = web3.Eth.GetContractTransactionHandler<TFunction>(); var handler = web3.Eth.GetContractTransactionHandler<TFunction>();
var receipt = Time.Wait(handler.SendRequestAndWaitForReceiptAsync(contractAddress, function)); var receipt = Time.Wait(handler.SendRequestAndWaitForReceiptAsync(contractAddress, function));
if (!receipt.Succeeded()) throw new Exception("Unable to perform contract transaction."); if (!receipt.Succeeded()) throw new Exception("Unable to perform contract transaction.");
return receipt.TransactionHash;
} }
public decimal? GetSyncedBlockNumber() public decimal? GetSyncedBlockNumber()

View File

@ -7,8 +7,8 @@ namespace CodexContractsPlugin
{ {
CodexContractsDeployment Deployment { get; } CodexContractsDeployment Deployment { get; }
void MintTestTokens(IHasEthAddress owner, TestToken testTokens); string MintTestTokens(IHasEthAddress owner, TestToken testTokens);
void MintTestTokens(EthAddress ethAddress, TestToken testTokens); string MintTestTokens(EthAddress ethAddress, TestToken testTokens);
TestToken GetTestTokenBalance(IHasEthAddress owner); TestToken GetTestTokenBalance(IHasEthAddress owner);
TestToken GetTestTokenBalance(EthAddress ethAddress); TestToken GetTestTokenBalance(EthAddress ethAddress);
} }
@ -27,15 +27,15 @@ namespace CodexContractsPlugin
public CodexContractsDeployment Deployment { get; } public CodexContractsDeployment Deployment { get; }
public void MintTestTokens(IHasEthAddress owner, TestToken testTokens) public string MintTestTokens(IHasEthAddress owner, TestToken testTokens)
{ {
MintTestTokens(owner.EthAddress, testTokens); return MintTestTokens(owner.EthAddress, testTokens);
} }
public void MintTestTokens(EthAddress ethAddress, TestToken testTokens) public string MintTestTokens(EthAddress ethAddress, TestToken testTokens)
{ {
var interaction = new ContractInteractions(log, gethNode); var interaction = new ContractInteractions(log, gethNode);
interaction.MintTestTokens(ethAddress, testTokens.Amount, Deployment.TokenAddress); return interaction.MintTestTokens(ethAddress, testTokens.Amount, Deployment.TokenAddress);
} }
public TestToken GetTestTokenBalance(IHasEthAddress owner) public TestToken GetTestTokenBalance(IHasEthAddress owner)

View File

@ -26,10 +26,10 @@ namespace CodexContractsPlugin
return gethNode.Call<GetTokenFunction, string>(marketplaceAddress, function); return gethNode.Call<GetTokenFunction, string>(marketplaceAddress, function);
} }
public void MintTestTokens(EthAddress address, decimal amount, string tokenAddress) public string MintTestTokens(EthAddress address, decimal amount, string tokenAddress)
{ {
log.Debug($"{amount} -> {address} (token: {tokenAddress})"); log.Debug($"{amount} -> {address} (token: {tokenAddress})");
MintTokens(address.Address, amount, tokenAddress); return MintTokens(address.Address, amount, tokenAddress);
} }
public decimal GetBalance(string tokenAddress, string account) public decimal GetBalance(string tokenAddress, string account)
@ -56,7 +56,7 @@ namespace CodexContractsPlugin
} }
} }
private void MintTokens(string account, decimal amount, string tokenAddress) private string MintTokens(string account, decimal amount, string tokenAddress)
{ {
log.Debug($"({tokenAddress}) {amount} --> {account}"); log.Debug($"({tokenAddress}) {amount} --> {account}");
if (string.IsNullOrEmpty(account)) throw new ArgumentException("Invalid arguments for MintTestTokens"); if (string.IsNullOrEmpty(account)) throw new ArgumentException("Invalid arguments for MintTestTokens");
@ -67,7 +67,7 @@ namespace CodexContractsPlugin
Amount = amount.ToBig() Amount = amount.ToBig()
}; };
gethNode.SendTransaction(tokenAddress, function); return gethNode.SendTransaction(tokenAddress, function);
} }
private bool IsBlockNumberOK() private bool IsBlockNumberOK()

View File

@ -13,10 +13,10 @@ namespace GethPlugin
Ether GetEthBalance(); Ether GetEthBalance();
Ether GetEthBalance(IHasEthAddress address); Ether GetEthBalance(IHasEthAddress address);
Ether GetEthBalance(EthAddress address); Ether GetEthBalance(EthAddress address);
void SendEth(IHasEthAddress account, Ether eth); string SendEth(IHasEthAddress account, Ether eth);
void SendEth(EthAddress account, Ether eth); string SendEth(EthAddress account, Ether eth);
TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new(); TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
void SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new(); string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new();
decimal? GetSyncedBlockNumber(); decimal? GetSyncedBlockNumber();
bool IsContractAvailable(string abi, string contractAddress); bool IsContractAvailable(string abi, string contractAddress);
GethBootstrapNode GetBootstrapRecord(); GethBootstrapNode GetBootstrapRecord();
@ -103,14 +103,14 @@ namespace GethPlugin
return StartInteraction().GetEthBalance(address.Address).Eth(); return StartInteraction().GetEthBalance(address.Address).Eth();
} }
public void SendEth(IHasEthAddress owner, Ether eth) public string SendEth(IHasEthAddress owner, Ether eth)
{ {
SendEth(owner.EthAddress, eth); return SendEth(owner.EthAddress, eth);
} }
public void SendEth(EthAddress account, Ether eth) public string SendEth(EthAddress account, Ether eth)
{ {
StartInteraction().SendEth(account.Address, eth.Eth); return StartInteraction().SendEth(account.Address, eth.Eth);
} }
public TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new() public TResult Call<TFunction, TResult>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
@ -118,9 +118,9 @@ namespace GethPlugin
return StartInteraction().Call<TFunction, TResult>(contractAddress, function); return StartInteraction().Call<TFunction, TResult>(contractAddress, function);
} }
public void SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new() public string SendTransaction<TFunction>(string contractAddress, TFunction function) where TFunction : FunctionMessage, new()
{ {
StartInteraction().SendTransaction(contractAddress, function); return StartInteraction().SendTransaction(contractAddress, function);
} }
public decimal? GetSyncedBlockNumber() public decimal? GetSyncedBlockNumber()

View File

@ -1,4 +1,5 @@
using Discord.WebSocket; using Discord;
using Discord.WebSocket;
namespace BiblioTech namespace BiblioTech
{ {
@ -7,6 +8,7 @@ namespace BiblioTech
private SocketGuild guild = null!; private SocketGuild guild = null!;
private ulong[] adminIds = Array.Empty<ulong>(); private ulong[] adminIds = Array.Empty<ulong>();
private DateTime lastUpdate = DateTime.MinValue; private DateTime lastUpdate = DateTime.MinValue;
private ISocketMessageChannel adminChannel = null!;
public void SetGuild(SocketGuild guild) public void SetGuild(SocketGuild guild)
{ {
@ -20,11 +22,21 @@ namespace BiblioTech
return adminIds.Contains(userId); return adminIds.Contains(userId);
} }
public bool IsAdminChannel(ISocketMessageChannel channel) public bool IsAdminChannel(IChannel channel)
{ {
return channel.Name == Program.Config.AdminChannelName; return channel.Name == Program.Config.AdminChannelName;
} }
public ISocketMessageChannel GetAdminChannel()
{
return adminChannel;
}
public void SetAdminChannel(ISocketMessageChannel adminChannel)
{
this.adminChannel = adminChannel;
}
private bool ShouldUpdate() private bool ShouldUpdate()
{ {
return !adminIds.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10); return !adminIds.Any() || (DateTime.UtcNow - lastUpdate) > TimeSpan.FromMinutes(10);

View File

@ -17,22 +17,24 @@ namespace BiblioTech
try try
{ {
Program.Log.Log($"Responding to '{Name}'");
var context = new CommandContext(command, command.Data.Options); var context = new CommandContext(command, command.Data.Options);
await command.RespondAsync(StartingMessage, ephemeral: IsEphemeral(context)); await command.RespondAsync(StartingMessage, ephemeral: IsEphemeral(context));
await Invoke(context); await Invoke(context);
} }
catch (Exception ex) catch (Exception ex)
{ {
var msg = "Failed with exception: " + ex;
if (IsInAdminChannel(command)) if (IsInAdminChannel(command))
{ {
var msg = "Failed with exception: " + ex;
await command.FollowupAsync(msg.Substring(0, Math.Min(1900, msg.Length))); await command.FollowupAsync(msg.Substring(0, Math.Min(1900, msg.Length)));
} }
else else
{ {
await command.FollowupAsync("Something failed while trying to do that...", ephemeral: true); await command.FollowupAsync("Something failed while trying to do that...", ephemeral: true);
await Program.AdminChecker.GetAdminChannel().SendMessageAsync(msg);
} }
Console.WriteLine(ex); Program.Log.Error(msg);
} }
} }

View File

@ -23,18 +23,26 @@ namespace BiblioTech
{ {
var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName); var guild = client.Guilds.Single(g => g.Name == Program.Config.ServerName);
Program.AdminChecker.SetGuild(guild); Program.AdminChecker.SetGuild(guild);
Program.Log.Log($"Initializing for guild: '{guild.Name}'");
var adminChannels = guild.TextChannels.Where(Program.AdminChecker.IsAdminChannel).ToArray();
if (adminChannels == null || !adminChannels.Any()) throw new Exception("No admin message channel");
Program.AdminChecker.SetAdminChannel(adminChannels.First());
var builders = commands.Select(c => var builders = commands.Select(c =>
{ {
var msg = $"Building command '{c.Name}' with options: ";
var builder = new SlashCommandBuilder() var builder = new SlashCommandBuilder()
.WithName(c.Name) .WithName(c.Name)
.WithDescription(c.Description); .WithDescription(c.Description);
foreach (var option in c.Options) foreach (var option in c.Options)
{ {
msg += option.Name + " ";
builder.AddOption(option.Build()); builder.AddOption(option.Build());
} }
Program.Log.Log(msg);
return builder; return builder;
}); });
@ -48,7 +56,7 @@ namespace BiblioTech
catch (HttpException exception) catch (HttpException exception)
{ {
var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented); var json = JsonConvert.SerializeObject(exception.Errors, Formatting.Indented);
Console.WriteLine(json); Program.Log.Error(json);
} }
} }

View File

@ -1,7 +1,5 @@
using BiblioTech.Options; using BiblioTech.Options;
using CodexContractsPlugin; using CodexContractsPlugin;
using CodexPlugin;
using Core;
using GethPlugin; using GethPlugin;
namespace BiblioTech.Commands namespace BiblioTech.Commands

View File

@ -43,29 +43,29 @@ namespace BiblioTech.Commands
await context.Followup(string.Join(Environment.NewLine, report)); await context.Followup(string.Join(Environment.NewLine, report));
} }
private TestToken ProcessTokens(ICodexContracts contracts, EthAddress addr, List<string> report) private Transaction<TestToken>? ProcessTokens(ICodexContracts contracts, EthAddress addr, List<string> report)
{ {
if (ShouldMintTestTokens(contracts, addr)) if (ShouldMintTestTokens(contracts, addr))
{ {
contracts.MintTestTokens(addr, defaultTestTokensToMint); var transaction = contracts.MintTestTokens(addr, defaultTestTokensToMint);
report.Add($"Minted {defaultTestTokensToMint}."); report.Add($"Minted {defaultTestTokensToMint} {FormatTransactionLink(transaction)}");
return defaultTestTokensToMint; return new Transaction<TestToken>(defaultTestTokensToMint, transaction);
} }
report.Add("TestToken balance over threshold."); report.Add("TestToken balance over threshold. (No TestTokens minted.)");
return 0.TestTokens(); return null;
} }
private Ether ProcessEth(IGethNode gethNode, EthAddress addr, List<string> report) private Transaction<Ether>? ProcessEth(IGethNode gethNode, EthAddress addr, List<string> report)
{ {
if (ShouldSendEth(gethNode, addr)) if (ShouldSendEth(gethNode, addr))
{ {
gethNode.SendEth(addr, defaultEthToSend); var transaction = gethNode.SendEth(addr, defaultEthToSend);
report.Add($"Sent {defaultEthToSend}."); report.Add($"Sent {defaultEthToSend} {FormatTransactionLink(transaction)}");
return defaultEthToSend; return new Transaction<Ether>(defaultEthToSend, transaction);
} }
report.Add("Eth balance is over threshold."); report.Add("Eth balance is over threshold. (No Eth sent.)");
return 0.Eth(); return null;
} }
private bool ShouldMintTestTokens(ICodexContracts contracts, EthAddress addr) private bool ShouldMintTestTokens(ICodexContracts contracts, EthAddress addr)
@ -79,5 +79,11 @@ namespace BiblioTech.Commands
var eth = gethNode.GetEthBalance(addr); var eth = gethNode.GetEthBalance(addr);
return eth.Eth < 1.0m; return eth.Eth < 1.0m;
} }
private string FormatTransactionLink(string transaction)
{
var url = $"https://explorer.testnet.codex.storage/tx/{transaction}";
return $"- [View on block explorer]({url}){Environment.NewLine}Transaction ID - `{transaction}`";
}
} }
} }

View File

@ -19,12 +19,6 @@ namespace BiblioTech
[Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")] [Uniform("admin-channel-name", "ac", "ADMINCHANNELNAME", true, "Name of the Discord server channel where admin commands are allowed.")]
public string AdminChannelName { get; set; } = "admin-channel"; public string AdminChannelName { get; set; } = "admin-channel";
[Uniform("kube-config", "kc", "KUBECONFIG", true, "Path to Kubeconfig file. Use a Kubeconfig with read-only access.")]
public string KubeConfigFile { get; set; } = "null";
[Uniform("kube-namespace", "kn", "KUBENAMESPACE", true, "Kubernetes namespace.")]
public string KubeNamespace { get; set; } = string.Empty;
public string EndpointsPath public string EndpointsPath
{ {
get get
@ -40,5 +34,13 @@ namespace BiblioTech
return Path.Combine(DataPath, "users"); return Path.Combine(DataPath, "users");
} }
} }
public string LogPath
{
get
{
return Path.Combine(DataPath, "logs");
}
}
} }
} }

View File

@ -2,6 +2,7 @@
using BiblioTech.Commands; using BiblioTech.Commands;
using Discord; using Discord;
using Discord.WebSocket; using Discord.WebSocket;
using Logging;
namespace BiblioTech namespace BiblioTech
{ {
@ -11,13 +12,19 @@ namespace BiblioTech
public static Configuration Config { get; private set; } = null!; public static Configuration Config { get; private set; } = null!;
public static UserRepo UserRepo { get; } = new UserRepo(); public static UserRepo UserRepo { get; } = new UserRepo();
public static AdminChecker AdminChecker { get; } = new AdminChecker(); public static AdminChecker AdminChecker { get; private set; } = null!;
public static ILog Log { get; private set; } = null!;
public static Task Main(string[] args) public static Task Main(string[] args)
{ {
var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args); var uniformArgs = new ArgsUniform<Configuration>(PrintHelp, args);
Config = uniformArgs.Parse(); Config = uniformArgs.Parse();
Log = new LogSplitter(
new FileLog(Path.Combine(Config.LogPath, "discordbot.log")),
new ConsoleLog()
);
EnsurePath(Config.DataPath); EnsurePath(Config.DataPath);
EnsurePath(Config.UserDataPath); EnsurePath(Config.UserDataPath);
EnsurePath(Config.EndpointsPath); EnsurePath(Config.EndpointsPath);
@ -27,9 +34,9 @@ namespace BiblioTech
public async Task MainAsync() public async Task MainAsync()
{ {
Console.WriteLine("Starting Codex Discord Bot..."); Log.Log("Starting Codex Discord Bot...");
client = new DiscordSocketClient(); client = new DiscordSocketClient();
client.Log += Log; client.Log += ClientLog;
var associateCommand = new UserAssociateCommand(); var associateCommand = new UserAssociateCommand();
var sprCommand = new SprCommand(); var sprCommand = new SprCommand();
@ -43,18 +50,21 @@ namespace BiblioTech
await client.LoginAsync(TokenType.Bot, Config.ApplicationToken); await client.LoginAsync(TokenType.Bot, Config.ApplicationToken);
await client.StartAsync(); await client.StartAsync();
Console.WriteLine("Running...");
AdminChecker = new AdminChecker();
Log.Log("Running...");
await Task.Delay(-1); await Task.Delay(-1);
} }
private static void PrintHelp() private static void PrintHelp()
{ {
Console.WriteLine("BiblioTech - Codex Discord Bot"); Log.Log("BiblioTech - Codex Discord Bot");
} }
private Task Log(LogMessage msg) private Task ClientLog(LogMessage msg)
{ {
Console.WriteLine(msg.ToString()); Log.Log("DiscordClient: " + msg.ToString());
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -19,7 +19,7 @@
public static string Get() public static string Get()
{ {
return messages[random.Next(messages.Length)]; return "Hold on: " + messages[random.Next(messages.Length)];
} }
} }
} }

View File

@ -0,0 +1,14 @@
namespace BiblioTech
{
public class Transaction<T>
{
public Transaction(T tokenAmount, string transactionHash)
{
TokenAmount = tokenAmount;
TransactionHash = transactionHash;
}
public T TokenAmount { get; }
public string TransactionHash { get; }
}
}

View File

@ -25,7 +25,7 @@ namespace BiblioTech
} }
} }
public void AddMintEventForUser(IUser user, EthAddress usedAddress, Ether eth, TestToken tokens) public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction<Ether>? eth, Transaction<TestToken>? tokens)
{ {
lock (repoLock) lock (repoLock)
{ {
@ -67,7 +67,14 @@ namespace BiblioTech
} }
foreach (var me in userData.MintEvents) foreach (var me in userData.MintEvents)
{ {
result.Add($"{me.Utc.ToString("o")} - Minted {me.EthReceived} and {me.TestTokensMinted} to {me.UsedAddress}."); if (me.EthReceived != null)
{
result.Add($"{me.Utc.ToString("o")} - Sent {me.EthReceived.TokenAmount} to {me.UsedAddress}. ({me.EthReceived.TransactionHash})");
}
if (me.TestTokensMinted != null)
{
result.Add($"{me.Utc.ToString("o")} - Minted {me.TestTokensMinted.TokenAmount} to {me.UsedAddress}. ({me.TestTokensMinted.TransactionHash})");
}
} }
} }
} }
@ -221,7 +228,7 @@ namespace BiblioTech
public class UserMintEvent public class UserMintEvent
{ {
public UserMintEvent(DateTime utc, EthAddress usedAddress, Ether ethReceived, TestToken testTokensMinted) public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction<Ether>? ethReceived, Transaction<TestToken>? testTokensMinted)
{ {
Utc = utc; Utc = utc;
UsedAddress = usedAddress; UsedAddress = usedAddress;
@ -231,7 +238,7 @@ namespace BiblioTech
public DateTime Utc { get; } public DateTime Utc { get; }
public EthAddress UsedAddress { get; } public EthAddress UsedAddress { get; }
public Ether EthReceived { get; } public Transaction<Ether>? EthReceived { get; }
public TestToken TestTokensMinted { get; } public Transaction<TestToken>? TestTokensMinted { get; }
} }
} }