From ff5484c0c9f699b2443d4ef46732a7106e7ec47d Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Sat, 4 Oct 2025 14:10:25 -0500 Subject: [PATCH] Added lastactive command to get the last time BossmanJack did something observed by the bot --- KfChatDotNetBot/Commands/UtilityCommands.cs | 35 ++++++++++++++++++ KfChatDotNetBot/Models/BotServicesModels.cs | 13 +++++++ KfChatDotNetBot/Services/BotServices.cs | 39 +++++++++++++++++++-- KfChatDotNetBot/Settings/BuiltIn.cs | 8 +++++ 4 files changed, 93 insertions(+), 2 deletions(-) diff --git a/KfChatDotNetBot/Commands/UtilityCommands.cs b/KfChatDotNetBot/Commands/UtilityCommands.cs index 624d881..d1dde28 100644 --- a/KfChatDotNetBot/Commands/UtilityCommands.cs +++ b/KfChatDotNetBot/Commands/UtilityCommands.cs @@ -1,7 +1,11 @@ ο»Ώusing System.Reflection; using System.Text.RegularExpressions; +using Humanizer; +using Humanizer.Localisation; +using KfChatDotNetBot.Extensions; using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; +using KfChatDotNetBot.Settings; using KfChatDotNetWsClient.Models.Events; namespace KfChatDotNetBot.Commands; @@ -62,4 +66,35 @@ public class GetVersionCommand : ICommand } await botInstance.SendChatMessageAsync($"Bot compiled against {version.Split('+')[1]}", true); } +} + +public class GetLastActivity : ICommand +{ + public List Patterns => [ + new Regex("^lastactivity", RegexOptions.IgnoreCase) + ]; + + public string? HelpText => "When was Bossman last active?"; + public UserRight RequiredRight => UserRight.Loser; + public TimeSpan Timeout => TimeSpan.FromSeconds(10); + public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel + { + MaxInvocations = 3, + Window = TimeSpan.FromSeconds(10) + }; + public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) + { + var lastActive = await SettingsProvider.GetValueAsync(BuiltIn.Keys.BossmanLastSighting); + if (lastActive.Value == null) + { + await botInstance.SendChatMessageAsync($"{user.FormatUsername()}, I don't know.", true); + return; + } + + var activity = lastActive.JsonDeserialize(); + var elapsed = DateTimeOffset.UtcNow - activity!.When; + await botInstance.SendChatMessageAsync( + $"{user.FormatUsername()}, BossmanJack was last seen {elapsed.Humanize(maxUnit: TimeUnit.Day, minUnit: TimeUnit.Second, precision: 2)} {activity.Activity}", + true); + } } \ No newline at end of file diff --git a/KfChatDotNetBot/Models/BotServicesModels.cs b/KfChatDotNetBot/Models/BotServicesModels.cs index 8f9b3e0..0ea623d 100644 --- a/KfChatDotNetBot/Models/BotServicesModels.cs +++ b/KfChatDotNetBot/Models/BotServicesModels.cs @@ -24,4 +24,17 @@ public class PartiChannelModel public required int ForumId { get; set; } public bool AutoCapture { get; set; } = false; public required string SocialMedia { get; set; } +} + +public class LastSightingModel +{ + /// + /// When Bossman was last seen + /// + public required DateTimeOffset When { get; set; } + /// + /// Where he was last seen. Message is formatted like "Bossman last seen 30 minutes ago {activity} + /// Suggestions: "going offline on Discord", "talking in Discord", "betting on Shuffle.us" + /// + public required string Activity { get; set; } } \ No newline at end of file diff --git a/KfChatDotNetBot/Services/BotServices.cs b/KfChatDotNetBot/Services/BotServices.cs index 70d03cb..74fe560 100644 --- a/KfChatDotNetBot/Services/BotServices.cs +++ b/KfChatDotNetBot/Services/BotServices.cs @@ -541,11 +541,11 @@ public class BotServices }); _logger.Info("Added a Bossman Rainbet bet to the database"); db.SaveChanges(); - + var wagered = float.Parse(bet.Value); _logger.Info("ALERT BMJ IS BETTING (on Rainbet)"); + UpdateBossmanLastSighting($"betting {wagered:C} {bet.CurrencyName} on {bet.Game.Name} at Rainbet").Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; - var wagered = float.Parse(bet.Value); var payout = float.Parse(bet.Payout); var payoutColor = settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value; if (payout < wagered) payoutColor = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value; @@ -565,6 +565,8 @@ public class BotServices return; } _logger.Info("ALERT BMJ IS BETTING (on Jackpot)"); + UpdateBossmanLastSighting($"betting {bet.Wager} {bet.Currency} on {bet.GameName} at Jackpot") + .Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; @@ -587,6 +589,9 @@ public class BotServices return; } _logger.Info("ALERT BMJ IS BETTING (on Clash.gg)"); + UpdateBossmanLastSighting( + $"betting {bet.Bet / 100.0:N2} {bet.Currency.Humanize()} on {bet.Game.Humanize()} at Clash.gg") + .Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; var username = settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value; @@ -623,6 +628,7 @@ public class BotServices return; } _logger.Info("ALERT BMJ IS BETTING (on BetBolt)"); + UpdateBossmanLastSighting($"betting {bet.BetAmountFiat:C} on {bet.GameName} at BetBolt").Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; var payoutColor = settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value; if (bet.WinAmountFiat < 0) payoutColor = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value; @@ -642,6 +648,7 @@ public class BotServices return; } _logger.Info("ALERT BMJ IS BETTING (on Yeet)"); + UpdateBossmanLastSighting($"betting {bet.BetAmount:C} on {bet.GameName} at Yeet").Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; //if (bet.WinAmountFiat < 0) payoutColor = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value; var msg = _chatBot.SendChatMessage($"🚨🚨 JEET BETTING 🚨🚨 {bet.Username} just bet {bet.BetAmount:C} worth of {bet.CurrencyCode} on {bet.GameName} πŸ’©πŸ’©", true); @@ -660,6 +667,7 @@ public class BotServices return; } _logger.Info("ALERT BMJ IS BETTING (on Yeet)"); + UpdateBossmanLastSighting($"betting {bet.BetAmount:C} on {bet.GameName} at Yeet").Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; var payoutColor = settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value; if (bet.Multiplier < 1) payoutColor = settings[BuiltIn.Keys.KiwiFarmsRedColor].Value; @@ -763,6 +771,7 @@ public class BotServices var discordIcon = SettingsProvider.GetValueAsync(BuiltIn.Keys.DiscordIcon).Result; var channelName = channel.Name ?? "Unknown name"; _chatBot.SendChatMessage($"[img]{discordIcon.Value}[/img] Discord {channel.Type.Humanize()} channel '{channelName}' was deleted 🚨🚨", true); + UpdateBossmanLastSighting($"deleting {channelName} on Discord").Wait(_cancellationToken); } private void DiscordOnChannelCreated(object sender, DiscordChannelCreationModel channel) @@ -773,6 +782,7 @@ public class BotServices var discordIcon = SettingsProvider.GetValueAsync(BuiltIn.Keys.DiscordIcon).Result; var channelName = channel.Name ?? "Unknown name"; _chatBot.SendChatMessage($"[img]{discordIcon.Value}[/img] New Discord {channel.Type.Humanize()} channel created: {channelName} 🚨🚨", true); + UpdateBossmanLastSighting($"creating {channelName} on Discord").Wait(_cancellationToken); } private void TwitchChatOnMessageReceived(object sender, string nick, string target, string message) @@ -784,8 +794,10 @@ public class BotServices // Not caching this value as it won't harm it to have to look this up in even the worst spergout sesh var twitchIcon = SettingsProvider.GetValueAsync(BuiltIn.Keys.TwitchIcon).Result.Value; _chatBot.SendChatMessage($"[img]{twitchIcon}[/img] {nick}: {message.TrimEnd('\r')}", true); + UpdateBossmanLastSighting("talking in Twitch chat").Wait(_cancellationToken); } + // TODO: Figure out why this never works private void DiscordOnPresenceUpdated(object sender, DiscordPresenceUpdateModel presence) { var settings = SettingsProvider.GetMultipleValuesAsync([BuiltIn.Keys.DiscordBmjId, BuiltIn.Keys.DiscordIcon]).Result; @@ -801,6 +813,7 @@ public class BotServices // _lastDiscordStatus = presence.Status; var clientStatus = presence.ClientStatus.Keys.Aggregate(string.Empty, (current, device) => current + $"{device} is {presence.ClientStatus[device]}; "); _chatBot.SendChatMessage($"[img]{settings[BuiltIn.Keys.DiscordIcon].Value}[/img] {presence.User.GlobalName ?? presence.User.Username} has updated his Discord presence: {clientStatus}"); + UpdateBossmanLastSighting($"going {presence.Status} on Discord").Wait(_cancellationToken); } private void DiscordOnMessageReceived(object sender, DiscordMessageModel message) @@ -832,12 +845,14 @@ public class BotServices _chatBot.SendChatMessage($"Verified [b]True and Honest[/b] by @KenoGPT at {bmt:dddd h:mm:ss tt} BMT", true); _ = DiscordFlashText(flashMsg); + UpdateBossmanLastSighting("going live on Discord").Wait(_cancellationToken); return; } if (message.Type == DiscordMessageType.StageEnd) { _chatBot.SendChatMessage($"[img]{settings[BuiltIn.Keys.DiscordIcon].Value}[/img] {message.Author.GlobalName ?? message.Author.Username} just ended a stage called {message.Content} :lossmanjack:", true); + UpdateBossmanLastSighting("ending a stage on Discord").Wait(_cancellationToken); return; } @@ -848,6 +863,7 @@ public class BotServices } _chatBot.SendChatMessage(result, TemporarilyBypassGambaSeshForDiscord); + UpdateBossmanLastSighting("talking in Discord").Wait(_cancellationToken); } private async Task DiscordFlashText(SentMessageTrackerModel msg) @@ -903,10 +919,12 @@ public class BotServices if (bet.Username == settings[BuiltIn.Keys.ShuffleBmjUsername].Value) { isDotUs = false; + UpdateBossmanLastSighting($"betting {bet.Amount} {bet.Currency} on {bet.GameName} at Shuffle.com").Wait(_cancellationToken); } else if (bet.Username == settings[BuiltIn.Keys.ShuffleDotUsBmjUsername].Value) { isDotUs = true; + UpdateBossmanLastSighting($"betting {bet.Amount} {bet.Currency} on {bet.GameName} at Shuffle.us").Wait(_cancellationToken); } else { @@ -947,9 +965,12 @@ public class BotServices CaptureYtDlpWorkingDirectory = settings[BuiltIn.Keys.CaptureStreamlinkBmjWorkingDirectory].Value }, _cancellationToken).CaptureAsync(); } + + UpdateBossmanLastSighting("going live on Twitch").Wait(_cancellationToken); return; } _chatBot.SendChatMessage($"{settings[BuiltIn.Keys.TwitchBossmanJackUsername].Value} is no longer live! :lossmanjack:", true); + UpdateBossmanLastSighting("ending stream on Twitch").Wait(_cancellationToken); } private void OnChipsggRecentBet(object sender, ChipsggBetModel bet) @@ -973,6 +994,8 @@ public class BotServices Currency = bet.Currency!, CurrencyPrice = bet.CurrencyPrice, BetId = bet.BetId ?? "0" }); db.SaveChanges(); + UpdateBossmanLastSighting($"betting {bet.Amount:N} {bet.Currency!.ToUpper()} on {bet.GameTitle} at Chips.gg") + .Wait(_cancellationToken); if (CheckBmjIsLive().Result) return; var payoutColor = settings[BuiltIn.Keys.KiwiFarmsGreenColor].Value; @@ -1029,6 +1052,7 @@ public class BotServices _logger.Debug("Message from BossmanJack"); _chatBot.SendChatMessage($"[img]{kickIcon.Value}[/img] BossmanJack: {e.Content.TranslateKickEmotes()}"); + UpdateBossmanLastSighting("talking in Kick chat").Wait(_cancellationToken); } private void OnStreamerIsLive(object sender, KickModels.StreamerIsLiveEventModel? e) @@ -1198,4 +1222,15 @@ public class BotServices } return false; } + + public async Task UpdateBossmanLastSighting(string activity) + { + _logger.Info($"Updating Bossman last sighting with: {activity}"); + var sighting = new LastSightingModel + { + When = DateTimeOffset.UtcNow, + Activity = activity + }; + await SettingsProvider.SetValueAsJsonObjectAsync(BuiltIn.Keys.BossmanLastSighting, sighting); + } } \ No newline at end of file diff --git a/KfChatDotNetBot/Settings/BuiltIn.cs b/KfChatDotNetBot/Settings/BuiltIn.cs index 16a09dc..19816c6 100644 --- a/KfChatDotNetBot/Settings/BuiltIn.cs +++ b/KfChatDotNetBot/Settings/BuiltIn.cs @@ -1076,6 +1076,13 @@ public static class BuiltIn Default = "true", ValueType = SettingValueType.Boolean, Regex = BooleanRegex + }, + new BuiltInSettingsModel + { + Key = Keys.BossmanLastSighting, + Description = "Object containing details of Bossman's last sighting", + Default = "{\n \"When\": \"2025-10-03T01:20:00-04:00\",\n \"Activity\": \"going to jail\"\n}", + ValueType = SettingValueType.Complex } ]; @@ -1200,5 +1207,6 @@ public static class BuiltIn public static string BotRateLimitExpiredEntryCleanupInterval = "Bot.RateLimit.ExpiredEntryCleanupInterval"; public static string CaptureStreamlinkBmjWorkingDirectory = "Bot.Streamlink.BmjWorkingDirectory"; public static string DiscordOnlySendSummariesIncludingBmj = "Discord.OnlySendSummariesIncludingBmj"; + public static string BossmanLastSighting = "Bot.BossmanLastSighting"; } } \ No newline at end of file