From 0ef55abdf49c4f4253de4851f72e6e2ff4ebdcd6 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 10:07:10 +0200 Subject: [PATCH 1/4] Adds events formatter --- .../ChainMonitor/ChainState.cs | 5 +- .../DoNothingChainEventHandler.cs | 5 +- Tools/TestNetRewarder/BufferLogger.cs | 41 ------ Tools/TestNetRewarder/ChainChangeMux.cs | 5 +- Tools/TestNetRewarder/EventsFormatter.cs | 122 ++++++++++++++++++ Tools/TestNetRewarder/MarketTracker.cs | 3 +- Tools/TestNetRewarder/Processor.cs | 22 ++-- Tools/TestNetRewarder/RewardCheck.cs | 3 +- 8 files changed, 142 insertions(+), 64 deletions(-) delete mode 100644 Tools/TestNetRewarder/BufferLogger.cs create mode 100644 Tools/TestNetRewarder/EventsFormatter.cs diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs index bc284eb..92169b7 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/ChainState.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin.Marketplace; +using GethPlugin; using Logging; using System.Numerics; using Utils; @@ -11,7 +12,7 @@ namespace CodexContractsPlugin.ChainMonitor void OnRequestFinished(IChainStateRequest request); void OnRequestFulfilled(IChainStateRequest request); void OnRequestCancelled(IChainStateRequest request); - void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex); + void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex); void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex); } @@ -116,7 +117,7 @@ namespace CodexContractsPlugin.ChainMonitor if (r == null) return; r.Hosts.Add(request.Host, (int)request.SlotIndex); r.Log($"[{request.Block.BlockNumber}] SlotFilled (host:'{request.Host}', slotIndex:{request.SlotIndex})"); - handler.OnSlotFilled(r, request.SlotIndex); + handler.OnSlotFilled(r, request.Host, request.SlotIndex); } private void ApplyEvent(SlotFreedEventDTO request) diff --git a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs index 8a3709c..41882c3 100644 --- a/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs +++ b/ProjectPlugins/CodexContractsPlugin/ChainMonitor/DoNothingChainEventHandler.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using GethPlugin; +using System.Numerics; namespace CodexContractsPlugin.ChainMonitor { @@ -20,7 +21,7 @@ namespace CodexContractsPlugin.ChainMonitor { } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { } diff --git a/Tools/TestNetRewarder/BufferLogger.cs b/Tools/TestNetRewarder/BufferLogger.cs deleted file mode 100644 index 1f32332..0000000 --- a/Tools/TestNetRewarder/BufferLogger.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Logging; - -namespace TestNetRewarder -{ - public class BufferLogger : ILog - { - private readonly List lines = new List(); - - public void AddStringReplace(string from, string to) - { - throw new NotImplementedException(); - } - - public LogFile CreateSubfile(string ext = "log") - { - throw new NotImplementedException(); - } - - public void Debug(string message = "", int skipFrames = 0) - { - lines.Add(message); - } - - public void Error(string message) - { - lines.Add($"Error: {message}"); - } - - public void Log(string message) - { - lines.Add(message); - } - - public string[] Get() - { - var result = lines.ToArray(); - lines.Clear(); - return result; - } - } -} diff --git a/Tools/TestNetRewarder/ChainChangeMux.cs b/Tools/TestNetRewarder/ChainChangeMux.cs index f70490b..740ba31 100644 --- a/Tools/TestNetRewarder/ChainChangeMux.cs +++ b/Tools/TestNetRewarder/ChainChangeMux.cs @@ -1,4 +1,5 @@ using CodexContractsPlugin.ChainMonitor; +using GethPlugin; using System.Numerics; namespace TestNetRewarder @@ -32,9 +33,9 @@ namespace TestNetRewarder foreach (var handler in handlers) handler.OnRequestFulfilled(request); } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { - foreach (var handler in handlers) handler.OnSlotFilled(request, slotIndex); + foreach (var handler in handlers) handler.OnSlotFilled(request, host, slotIndex); } public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) diff --git a/Tools/TestNetRewarder/EventsFormatter.cs b/Tools/TestNetRewarder/EventsFormatter.cs new file mode 100644 index 0000000..0ddf6ee --- /dev/null +++ b/Tools/TestNetRewarder/EventsFormatter.cs @@ -0,0 +1,122 @@ +using CodexContractsPlugin; +using CodexContractsPlugin.ChainMonitor; +using GethPlugin; +using System.Numerics; +using Utils; + +namespace TestNetRewarder +{ + public class EventsFormatter : IChainStateChangeHandler + { + private static readonly string nl = Environment.NewLine; + private readonly List events = new List(); + + public string[] GetEvents() + { + var result = events.ToArray(); + events.Clear(); + return result; + } + + public void AddError(string error) + { + AddBlock("📢 **Error**", error); + } + + public void OnNewRequest(IChainStateRequest request) + { + AddRequestBlock(request, "New Request", + $"Client: {request.Client}", + $"Content: {request.Request.Content.Cid}", + $"Duration: {BigIntToDuration(request.Request.Ask.Duration)}", + $"Expiry: {BigIntToDuration(request.Request.Expiry)}", + $"Collateral: {BitIntToTestTokens(request.Request.Ask.Collateral)}", + $"Reward: {BitIntToTestTokens(request.Request.Ask.Reward)}", + $"Number of Slots: {request.Request.Ask.Slots}", + $"Slot Tolerance: {request.Request.Ask.MaxSlotLoss}", + $"Slot Size: {BigIntToByteSize(request.Request.Ask.SlotSize)}" + ); + } + + public void OnRequestCancelled(IChainStateRequest request) + { + AddRequestBlock(request, "Cancelled"); + } + + public void OnRequestFinished(IChainStateRequest request) + { + AddRequestBlock(request, "Finished"); + } + + public void OnRequestFulfilled(IChainStateRequest request) + { + AddRequestBlock(request, "Started"); + } + + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) + { + AddRequestBlock(request, "Slot Filled", + $"Host: {host}", + $"Slot Index: {slotIndex}" + ); + } + + public void OnSlotFreed(IChainStateRequest request, BigInteger slotIndex) + { + AddRequestBlock(request, "Slot Freed", + $"Slot Index: {slotIndex}" + ); + } + + private void AddRequestBlock(IChainStateRequest request, string eventName, params string[] content) + { + var blockNumber = $"[{request.Request.Block.BlockNumber}]"; + var title = $"{blockNumber} **{eventName}** `{request.Request.Id}`"; + AddBlock(title, content); + } + + private void AddBlock(string title, params string[] content) + { + events.Add(FormatBlock(title, content)); + } + + private string FormatBlock(string title, params string[] content) + { + if (content == null || !content.Any()) + { + return $"{title}{nl}{nl}"; + } + + return string.Join(nl, + new string[] + { + title, + "```" + } + .Concat(content) + .Concat(new string[] + { + "```" + }) + ) + nl + nl; + } + + private string BigIntToDuration(BigInteger big) + { + var span = TimeSpan.FromSeconds((int)big); + return Time.FormatDuration(span); + } + + private string BigIntToByteSize(BigInteger big) + { + var size = new ByteSize((long)big); + return size.ToString(); + } + + private string BitIntToTestTokens(BigInteger big) + { + var tt = new TestToken(big); + return tt.ToString(); + } + } +} diff --git a/Tools/TestNetRewarder/MarketTracker.cs b/Tools/TestNetRewarder/MarketTracker.cs index 669dd3a..a850e32 100644 --- a/Tools/TestNetRewarder/MarketTracker.cs +++ b/Tools/TestNetRewarder/MarketTracker.cs @@ -1,5 +1,6 @@ using CodexContractsPlugin.ChainMonitor; using DiscordRewards; +using GethPlugin; using Logging; using System.Numerics; @@ -48,7 +49,7 @@ namespace TestNetRewarder { } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { } diff --git a/Tools/TestNetRewarder/Processor.cs b/Tools/TestNetRewarder/Processor.cs index f43a924..642be07 100644 --- a/Tools/TestNetRewarder/Processor.cs +++ b/Tools/TestNetRewarder/Processor.cs @@ -10,7 +10,7 @@ namespace TestNetRewarder private readonly RequestBuilder builder; private readonly RewardChecker rewardChecker; private readonly MarketTracker marketTracker; - private readonly BufferLogger bufferLogger; + private readonly EventsFormatter eventsFormatter; private readonly ChainState chainState; private readonly BotClient client; private readonly ILog log; @@ -23,14 +23,15 @@ namespace TestNetRewarder builder = new RequestBuilder(); rewardChecker = new RewardChecker(builder); marketTracker = new MarketTracker(config, log); - bufferLogger = new BufferLogger(); + eventsFormatter = new EventsFormatter(); var handler = new ChainChangeMux( rewardChecker.Handler, - marketTracker + marketTracker, + eventsFormatter ); - chainState = new ChainState(new LogSplitter(log, bufferLogger), contracts, handler, config.HistoryStartUtc); + chainState = new ChainState(log, contracts, handler, config.HistoryStartUtc); } public async Task OnNewSegment(TimeRange timeRange) @@ -40,9 +41,9 @@ namespace TestNetRewarder chainState.Update(timeRange.To); var averages = marketTracker.GetAverages(); - var lines = RemoveFirstLine(bufferLogger.Get()); + var events = eventsFormatter.GetEvents(); - var request = builder.Build(averages, lines); + var request = builder.Build(averages, events); if (request.HasAny()) { await client.SendRewards(request); @@ -52,16 +53,9 @@ namespace TestNetRewarder { var msg = "Exception processing time segment: " + ex; log.Error(msg); - bufferLogger.Error(msg); + eventsFormatter.AddError(msg); throw; } } - - private string[] RemoveFirstLine(string[] lines) - { - //if (!lines.Any()) return Array.Empty(); - //return lines.Skip(1).ToArray(); - return lines; - } } } diff --git a/Tools/TestNetRewarder/RewardCheck.cs b/Tools/TestNetRewarder/RewardCheck.cs index 9be87d2..0132f04 100644 --- a/Tools/TestNetRewarder/RewardCheck.cs +++ b/Tools/TestNetRewarder/RewardCheck.cs @@ -53,11 +53,10 @@ namespace TestNetRewarder } } - public void OnSlotFilled(IChainStateRequest request, BigInteger slotIndex) + public void OnSlotFilled(IChainStateRequest request, EthAddress host, BigInteger slotIndex) { if (MeetsRequirements(CheckType.HostFilledSlot, request)) { - var host = request.Hosts.GetHost((int)slotIndex); if (host != null) { GiveReward(reward, host); From 1eb30329c6171878707dd6aeb9da2d2197646b31 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 10:14:23 +0200 Subject: [PATCH 2/4] Pulls out chain events sender --- Tools/BiblioTech/Rewards/ChainEventsSender.cs | 40 +++++++++++++++++++ Tools/BiblioTech/Rewards/RoleDriver.cs | 29 ++------------ 2 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 Tools/BiblioTech/Rewards/ChainEventsSender.cs diff --git a/Tools/BiblioTech/Rewards/ChainEventsSender.cs b/Tools/BiblioTech/Rewards/ChainEventsSender.cs new file mode 100644 index 0000000..2e1e9ec --- /dev/null +++ b/Tools/BiblioTech/Rewards/ChainEventsSender.cs @@ -0,0 +1,40 @@ +using Discord.WebSocket; +using Logging; + +namespace BiblioTech.Rewards +{ + public class ChainEventsSender + { + private readonly ILog log; + private SocketTextChannel? eventsChannel; + + public ChainEventsSender(ILog log, SocketTextChannel? eventsChannel) + { + this.log = log; + this.eventsChannel = eventsChannel; + } + + public async Task ProcessChainEvents(string[] eventsOverview) + { + if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; + try + { + await Task.Run(async () => + { + foreach (var e in eventsOverview) + { + if (!string.IsNullOrEmpty(e)) + { + await eventsChannel.SendMessageAsync(e); + await Task.Delay(3000); + } + } + }); + } + catch (Exception ex) + { + log.Error("Failed to process chain events: " + ex); + } + } + } +} diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index c7173c1..6f1819d 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -11,7 +11,7 @@ namespace BiblioTech.Rewards private readonly DiscordSocketClient client; private readonly ILog log; private readonly SocketTextChannel? rewardsChannel; - private readonly SocketTextChannel? eventsChannel; + private readonly ChainEventsSender eventsSender; private readonly RewardRepo repo = new RewardRepo(); public RoleDriver(DiscordSocketClient client, ILog log) @@ -19,7 +19,7 @@ namespace BiblioTech.Rewards this.client = client; this.log = log; rewardsChannel = GetChannel(Program.Config.RewardsChannelId); - eventsChannel = GetChannel(Program.Config.ChainEventsChannelId); + eventsSender = new ChainEventsSender(log, GetChannel(Program.Config.ChainEventsChannelId)); } public async Task GiveRewards(GiveRewardsCommand rewards) @@ -31,7 +31,7 @@ namespace BiblioTech.Rewards await ProcessRewards(rewards); } - await ProcessChainEvents(rewards.EventsOverview); + await eventsSender.ProcessChainEvents(rewards.EventsOverview); } private async Task ProcessRewards(GiveRewardsCommand rewards) @@ -60,29 +60,6 @@ namespace BiblioTech.Rewards return GetGuild().TextChannels.SingleOrDefault(c => c.Id == id); } - private async Task ProcessChainEvents(string[] eventsOverview) - { - if (eventsChannel == null || eventsOverview == null || !eventsOverview.Any()) return; - try - { - await Task.Run(async () => - { - foreach (var e in eventsOverview) - { - if (!string.IsNullOrEmpty(e)) - { - await eventsChannel.SendMessageAsync(e); - await Task.Delay(3000); - } - } - }); - } - catch (Exception ex) - { - log.Error("Failed to process chain events: " + ex); - } - } - private async Task> LoadAllUsers(SocketGuild guild) { log.Log("Loading all users.."); From 01ee514c730d0f5b1baf8b09ab5ff7fbc797be53 Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 10:44:37 +0200 Subject: [PATCH 3/4] Implements known username replacement --- Tools/BiblioTech/Rewards/ChainEventsSender.cs | 26 ++++- Tools/BiblioTech/UserData.cs | 66 ++++++++++++ Tools/BiblioTech/UserRepo.cs | 102 ++++++++---------- 3 files changed, 135 insertions(+), 59 deletions(-) create mode 100644 Tools/BiblioTech/UserData.cs diff --git a/Tools/BiblioTech/Rewards/ChainEventsSender.cs b/Tools/BiblioTech/Rewards/ChainEventsSender.cs index 2e1e9ec..bbad804 100644 --- a/Tools/BiblioTech/Rewards/ChainEventsSender.cs +++ b/Tools/BiblioTech/Rewards/ChainEventsSender.cs @@ -21,11 +21,14 @@ namespace BiblioTech.Rewards { await Task.Run(async () => { + var users = Program.UserRepo.GetAllUserData(); + foreach (var e in eventsOverview) { if (!string.IsNullOrEmpty(e)) { - await eventsChannel.SendMessageAsync(e); + var @event = ApplyReplacements(users, e); + await eventsChannel.SendMessageAsync(@event); await Task.Delay(3000); } } @@ -36,5 +39,26 @@ namespace BiblioTech.Rewards log.Error("Failed to process chain events: " + ex); } } + + private string ApplyReplacements(UserData[] users, string msg) + { + var result = ApplyUserAddressReplacements(users, msg); + return result; + } + + private string ApplyUserAddressReplacements(UserData[] users, string msg) + { + foreach (var user in users) + { + if (user.CurrentAddress != null && + !string.IsNullOrEmpty(user.CurrentAddress.Address) && + !string.IsNullOrEmpty(user.Name)) + { + msg = msg.Replace(user.CurrentAddress.Address, user.Name); + } + } + + return msg; + } } } diff --git a/Tools/BiblioTech/UserData.cs b/Tools/BiblioTech/UserData.cs new file mode 100644 index 0000000..540c326 --- /dev/null +++ b/Tools/BiblioTech/UserData.cs @@ -0,0 +1,66 @@ +using CodexContractsPlugin; +using GethPlugin; + +namespace BiblioTech +{ + public class UserData + { + public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List associateEvents, List mintEvents, bool notificationsEnabled) + { + DiscordId = discordId; + Name = name; + CreatedUtc = createdUtc; + CurrentAddress = currentAddress; + AssociateEvents = associateEvents; + MintEvents = mintEvents; + NotificationsEnabled = notificationsEnabled; + } + + public ulong DiscordId { get; } + public string Name { get; } + public DateTime CreatedUtc { get; } + public EthAddress? CurrentAddress { get; set; } + public List AssociateEvents { get; } + public List MintEvents { get; } + public bool NotificationsEnabled { get; set; } + + public string[] CreateOverview() + { + return new[] + { + $"name: '{Name}' - id:{DiscordId}", + $"joined: {CreatedUtc.ToString("o")}", + $"current address: {CurrentAddress}", + $"{AssociateEvents.Count + MintEvents.Count} total bot events." + }; + } + } + + public class UserAssociateAddressEvent + { + public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress) + { + Utc = utc; + NewAddress = newAddress; + } + + public DateTime Utc { get; } + public EthAddress? NewAddress { get; } + } + + public class UserMintEvent + { + public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction? ethReceived, Transaction? testTokensMinted) + { + Utc = utc; + UsedAddress = usedAddress; + EthReceived = ethReceived; + TestTokensMinted = testTokensMinted; + } + + public DateTime Utc { get; } + public EthAddress UsedAddress { get; } + public Transaction? EthReceived { get; } + public Transaction? TestTokensMinted { get; } + } +} diff --git a/Tools/BiblioTech/UserRepo.cs b/Tools/BiblioTech/UserRepo.cs index 01afbe9..7cd03fd 100644 --- a/Tools/BiblioTech/UserRepo.cs +++ b/Tools/BiblioTech/UserRepo.cs @@ -8,6 +8,7 @@ namespace BiblioTech public class UserRepo { private readonly object repoLock = new object(); + private readonly Dictionary cache = new Dictionary(); public bool AssociateUserWithAddress(IUser user, EthAddress address) { @@ -33,6 +34,12 @@ namespace BiblioTech } } + public UserData[] GetAllUserData() + { + if (cache.Count == 0) LoadAllUserData(); + return cache.Values.ToArray(); + } + public void AddMintEventForUser(IUser user, EthAddress usedAddress, Transaction? eth, Transaction? tokens) { lock (repoLock) @@ -151,12 +158,19 @@ namespace BiblioTech private UserData? GetUserData(IUser user) { + if (cache.ContainsKey(user.Id)) + { + return cache[user.Id]; + } + var filename = GetFilename(user); if (!File.Exists(filename)) { return null; } - return JsonConvert.DeserializeObject(File.ReadAllText(filename))!; + var userData = JsonConvert.DeserializeObject(File.ReadAllText(filename))!; + cache.Add(userData.DiscordId, userData); + return userData; } private UserData GetOrCreate(IUser user) @@ -181,6 +195,15 @@ namespace BiblioTech var filename = GetFilename(userData); if (File.Exists(filename)) File.Delete(filename); File.WriteAllText(filename, JsonConvert.SerializeObject(userData)); + + if (cache.ContainsKey(userData.DiscordId)) + { + cache[userData.DiscordId] = userData; + } + else + { + cache.Add(userData.DiscordId, userData); + } } private static string GetFilename(IUser user) @@ -197,66 +220,29 @@ namespace BiblioTech { return Path.Combine(Program.Config.UserDataPath, discordId.ToString() + ".json"); } - } - public class UserData - { - public UserData(ulong discordId, string name, DateTime createdUtc, EthAddress? currentAddress, List associateEvents, List mintEvents, bool notificationsEnabled) + private void LoadAllUserData() { - DiscordId = discordId; - Name = name; - CreatedUtc = createdUtc; - CurrentAddress = currentAddress; - AssociateEvents = associateEvents; - MintEvents = mintEvents; - NotificationsEnabled = notificationsEnabled; - } - - public ulong DiscordId { get; } - public string Name { get; } - public DateTime CreatedUtc { get; } - public EthAddress? CurrentAddress { get; set; } - public List AssociateEvents { get; } - public List MintEvents { get; } - public bool NotificationsEnabled { get; set; } - - public string[] CreateOverview() - { - return new[] + try { - $"name: '{Name}' - id:{DiscordId}", - $"joined: {CreatedUtc.ToString("o")}", - $"current address: {CurrentAddress}", - $"{AssociateEvents.Count + MintEvents.Count} total bot events." - }; + var files = Directory.GetFiles(Program.Config.UserDataPath); + foreach (var file in files) + { + try + { + var userData = JsonConvert.DeserializeObject(File.ReadAllText(file))!; + if (userData != null && userData.DiscordId > 0) + { + cache.Add(userData.DiscordId, userData); + } + } + catch { } + } + } + catch (Exception ex) + { + Program.Log.Error("Exception while trying to load all user data: " + ex); + } } } - - public class UserAssociateAddressEvent - { - public UserAssociateAddressEvent(DateTime utc, EthAddress? newAddress) - { - Utc = utc; - NewAddress = newAddress; - } - - public DateTime Utc { get; } - public EthAddress? NewAddress { get; } - } - - public class UserMintEvent - { - public UserMintEvent(DateTime utc, EthAddress usedAddress, Transaction? ethReceived, Transaction? testTokensMinted) - { - Utc = utc; - UsedAddress = usedAddress; - EthReceived = ethReceived; - TestTokensMinted = testTokensMinted; - } - - public DateTime Utc { get; } - public EthAddress UsedAddress { get; } - public Transaction? EthReceived { get; } - public Transaction? TestTokensMinted { get; } - } } From ba43fd90c6a5dea68465ea2593dfdafa6424d9ea Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 27 Jun 2024 11:16:17 +0200 Subject: [PATCH 4/4] Adds admin option to set custom replacements --- Tools/BiblioTech/CommandHandler.cs | 6 +- Tools/BiblioTech/Commands/AdminCommand.cs | 62 ++++++++++++++++++- Tools/BiblioTech/Program.cs | 5 +- Tools/BiblioTech/Rewards/ChainEventsSender.cs | 12 +++- Tools/BiblioTech/Rewards/CustomReplacement.cs | 34 ++++++++++ Tools/BiblioTech/Rewards/RoleDriver.cs | 4 +- 6 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 Tools/BiblioTech/Rewards/CustomReplacement.cs diff --git a/Tools/BiblioTech/CommandHandler.cs b/Tools/BiblioTech/CommandHandler.cs index 4f6d7b8..29a1777 100644 --- a/Tools/BiblioTech/CommandHandler.cs +++ b/Tools/BiblioTech/CommandHandler.cs @@ -10,12 +10,14 @@ namespace BiblioTech public class CommandHandler { private readonly DiscordSocketClient client; + private readonly CustomReplacement replacement; private readonly BaseCommand[] commands; private readonly ILog log; - public CommandHandler(ILog log, DiscordSocketClient client, params BaseCommand[] commands) + public CommandHandler(ILog log, DiscordSocketClient client, CustomReplacement replacement, params BaseCommand[] commands) { this.client = client; + this.replacement = replacement; this.commands = commands; this.log = log; client.Ready += Client_Ready; @@ -31,7 +33,7 @@ namespace BiblioTech 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()); - Program.RoleDriver = new RoleDriver(client, log); + Program.RoleDriver = new RoleDriver(client, log, replacement); var builders = commands.Select(c => { diff --git a/Tools/BiblioTech/Commands/AdminCommand.cs b/Tools/BiblioTech/Commands/AdminCommand.cs index 698fb00..d80326f 100644 --- a/Tools/BiblioTech/Commands/AdminCommand.cs +++ b/Tools/BiblioTech/Commands/AdminCommand.cs @@ -1,4 +1,5 @@ using BiblioTech.Options; +using BiblioTech.Rewards; namespace BiblioTech.Commands { @@ -10,12 +11,14 @@ namespace BiblioTech.Commands private readonly AddSprCommand addSprCommand; private readonly ClearSprsCommand clearSprsCommand; private readonly GetSprCommand getSprCommand; + private readonly LogReplaceCommand logReplaceCommand; - public AdminCommand(SprCommand sprCommand) + public AdminCommand(SprCommand sprCommand, CustomReplacement replacement) { addSprCommand = new AddSprCommand(sprCommand); clearSprsCommand = new ClearSprsCommand(sprCommand); getSprCommand = new GetSprCommand(sprCommand); + logReplaceCommand = new LogReplaceCommand(replacement); } public override string Name => "admin"; @@ -29,7 +32,8 @@ namespace BiblioTech.Commands whoIsCommand, addSprCommand, clearSprsCommand, - getSprCommand + getSprCommand, + logReplaceCommand }; protected override async Task Invoke(CommandContext context) @@ -52,6 +56,7 @@ namespace BiblioTech.Commands await addSprCommand.CommandHandler(context); await clearSprsCommand.CommandHandler(context); await getSprCommand.CommandHandler(context); + await logReplaceCommand.CommandHandler(context); } public class ClearUserAssociationCommand : SubCommandOption @@ -194,7 +199,7 @@ namespace BiblioTech.Commands } } - public class GetSprCommand: SubCommandOption + public class GetSprCommand : SubCommandOption { private readonly SprCommand sprCommand; @@ -210,5 +215,56 @@ namespace BiblioTech.Commands await context.Followup("SPRs: " + string.Join(", ", sprCommand.Get().Select(s => $"'{s}'"))); } } + + public class LogReplaceCommand : SubCommandOption + { + private readonly CustomReplacement replacement; + private readonly StringOption fromOption = new StringOption("from", "string to replace", true); + private readonly StringOption toOption = new StringOption("to", "string to replace with", false); + + public LogReplaceCommand(CustomReplacement replacement) + : base(name: "logreplace", + description: "Replaces all occurances of 'from' with 'to' in ChainEvent messages. Leave 'to' empty to remove a replacement.") + { + this.replacement = replacement; + } + + public override CommandOption[] Options => new[] { fromOption, toOption }; + + protected override async Task onSubCommand(CommandContext context) + { + var from = await fromOption.Parse(context); + var to = await toOption.Parse(context); + + if (string.IsNullOrEmpty(from)) + { + await context.Followup("'from' not received"); + return; + } + + if (from.Length < 5) + { + await context.Followup("'from' must be length 5 or greater."); + return; + } + + if (string.IsNullOrEmpty(to)) + { + replacement.Remove(from); + await context.Followup($"Replace for '{from}' removed."); + } + else + { + if (to.Length < 5) + { + await context.Followup("'to' must be length 5 or greater."); + return; + } + + replacement.Add(from, to); + await context.Followup($"Replace added '{from}' -->> '{to}'."); + } + } + } } } diff --git a/Tools/BiblioTech/Program.cs b/Tools/BiblioTech/Program.cs index 39bd58b..587c3a8 100644 --- a/Tools/BiblioTech/Program.cs +++ b/Tools/BiblioTech/Program.cs @@ -11,6 +11,7 @@ namespace BiblioTech public class Program { private DiscordSocketClient client = null!; + private readonly CustomReplacement replacement = new CustomReplacement(); public static Configuration Config { get; private set; } = null!; public static UserRepo UserRepo { get; } = new UserRepo(); @@ -73,13 +74,13 @@ namespace BiblioTech var notifyCommand = new NotifyCommand(); var associateCommand = new UserAssociateCommand(notifyCommand); var sprCommand = new SprCommand(); - var handler = new CommandHandler(Log, client, + var handler = new CommandHandler(Log, client, replacement, new GetBalanceCommand(associateCommand), new MintCommand(associateCommand), sprCommand, associateCommand, notifyCommand, - new AdminCommand(sprCommand), + new AdminCommand(sprCommand, replacement), new MarketCommand() ); diff --git a/Tools/BiblioTech/Rewards/ChainEventsSender.cs b/Tools/BiblioTech/Rewards/ChainEventsSender.cs index bbad804..0ec88d1 100644 --- a/Tools/BiblioTech/Rewards/ChainEventsSender.cs +++ b/Tools/BiblioTech/Rewards/ChainEventsSender.cs @@ -6,11 +6,13 @@ namespace BiblioTech.Rewards public class ChainEventsSender { private readonly ILog log; - private SocketTextChannel? eventsChannel; + private readonly CustomReplacement replacement; + private readonly SocketTextChannel? eventsChannel; - public ChainEventsSender(ILog log, SocketTextChannel? eventsChannel) + public ChainEventsSender(ILog log, CustomReplacement replacement, SocketTextChannel? eventsChannel) { this.log = log; + this.replacement = replacement; this.eventsChannel = eventsChannel; } @@ -43,6 +45,7 @@ namespace BiblioTech.Rewards private string ApplyReplacements(UserData[] users, string msg) { var result = ApplyUserAddressReplacements(users, msg); + result = ApplyCustomReplacements(result); return result; } @@ -60,5 +63,10 @@ namespace BiblioTech.Rewards return msg; } + + private string ApplyCustomReplacements(string result) + { + return replacement.Apply(result); + } } } diff --git a/Tools/BiblioTech/Rewards/CustomReplacement.cs b/Tools/BiblioTech/Rewards/CustomReplacement.cs new file mode 100644 index 0000000..359411a --- /dev/null +++ b/Tools/BiblioTech/Rewards/CustomReplacement.cs @@ -0,0 +1,34 @@ +namespace BiblioTech.Rewards +{ + public class CustomReplacement + { + private readonly Dictionary replacements = new Dictionary(); + + public void Add(string from, string to) + { + if (replacements.ContainsKey(from)) + { + replacements[from] = to; + } + else + { + replacements.Add(from, to); + } + } + + public void Remove(string from) + { + replacements.Remove(from); + } + + public string Apply(string msg) + { + var result = msg; + foreach (var pair in replacements) + { + result.Replace(pair.Key, pair.Value); + } + return result; + } + } +} diff --git a/Tools/BiblioTech/Rewards/RoleDriver.cs b/Tools/BiblioTech/Rewards/RoleDriver.cs index 6f1819d..71019b7 100644 --- a/Tools/BiblioTech/Rewards/RoleDriver.cs +++ b/Tools/BiblioTech/Rewards/RoleDriver.cs @@ -14,12 +14,12 @@ namespace BiblioTech.Rewards private readonly ChainEventsSender eventsSender; private readonly RewardRepo repo = new RewardRepo(); - public RoleDriver(DiscordSocketClient client, ILog log) + public RoleDriver(DiscordSocketClient client, ILog log, CustomReplacement replacement) { this.client = client; this.log = log; rewardsChannel = GetChannel(Program.Config.RewardsChannelId); - eventsSender = new ChainEventsSender(log, GetChannel(Program.Config.ChainEventsChannelId)); + eventsSender = new ChainEventsSender(log, replacement, GetChannel(Program.Config.ChainEventsChannelId)); } public async Task GiveRewards(GiveRewardsCommand rewards)