From 0dcbb25fe34fded22b5af81506e9de9bc1b943a5 Mon Sep 17 00:00:00 2001 From: barelyprofessional <150058423+barelyprofessional@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:02:09 -0600 Subject: [PATCH] Migrated moods and prompts to the settings. Removed the weird concurrent dictionary and replaced with Redis. Removed the cleanup watchdog in favor of Redis expiration --- KfChatDotNetBot/Commands/NoraCommand.cs | 98 ++++--------- KfChatDotNetBot/Commands/NoraMoods.cs | 37 ----- KfChatDotNetBot/Commands/NoraPrompt.txt | 32 ---- KfChatDotNetBot/KfChatDotNetBot.csproj | 3 - KfChatDotNetBot/Services/BotServices.cs | 1 - .../Services/ConversationContextManager.cs | 137 +++++++++++------- KfChatDotNetBot/Settings/BuiltIn.cs | 8 + 7 files changed, 126 insertions(+), 190 deletions(-) delete mode 100644 KfChatDotNetBot/Commands/NoraMoods.cs delete mode 100644 KfChatDotNetBot/Commands/NoraPrompt.txt diff --git a/KfChatDotNetBot/Commands/NoraCommand.cs b/KfChatDotNetBot/Commands/NoraCommand.cs index c0290fc..921a06c 100644 --- a/KfChatDotNetBot/Commands/NoraCommand.cs +++ b/KfChatDotNetBot/Commands/NoraCommand.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using Humanizer; using KfChatDotNetBot.Extensions; using KfChatDotNetBot.Models; using KfChatDotNetBot.Models.DbModels; @@ -36,70 +37,28 @@ public class NoraCommand : ICommand { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static readonly string? PromptFilePath = FindPromptFile(); - private static string? _cachedPrompt; - private static DateTime _cachedPromptLastModified; - - private static string? FindPromptFile() - { - // Check next to the executable first, then fall back to the Commands directory in the source tree - var exeDir = AppContext.BaseDirectory; - var candidate = Path.Combine(exeDir, "Commands", "NoraPrompt.txt"); - if (File.Exists(candidate)) return candidate; - - candidate = Path.Combine(exeDir, "NoraPrompt.txt"); - if (File.Exists(candidate)) return candidate; - - Logger.Error("NoraPrompt.txt not found in {exeDir}/Commands/ or {exeDir}/", exeDir); - return null; - } - - private static string? LoadPrompt() - { - if (PromptFilePath == null) return null; - - var lastWrite = File.GetLastWriteTimeUtc(PromptFilePath); - if (_cachedPrompt != null && lastWrite == _cachedPromptLastModified) - return _cachedPrompt; - - try - { - _cachedPrompt = File.ReadAllText(PromptFilePath).Trim(); - _cachedPromptLastModified = lastWrite; - Logger.Info("Loaded Nora prompt from {path} ({length} chars)", PromptFilePath, _cachedPrompt.Length); - return _cachedPrompt; - } - catch (Exception ex) - { - Logger.Error(ex, "Failed to read NoraPrompt.txt"); - return null; - } - } - public List Patterns => [ new Regex(@"^nora\s+(?.+)", RegexOptions.IgnoreCase) ]; - public string? HelpText => "Ask Nora AI a question (max 15 words, 140 chars). Use '!nora reset' to clear context."; + public string HelpText => "Ask Nora AI a question (max 15 words, 140 chars). Use '!nora reset' to clear context."; public UserRight RequiredRight => UserRight.Loser; public TimeSpan Timeout => TimeSpan.FromSeconds(30); - public RateLimitOptionsModel? RateLimitOptions => new RateLimitOptionsModel + public RateLimitOptionsModel RateLimitOptions => new() { Window = TimeSpan.FromMinutes(1), MaxInvocations = 3, Flags = RateLimitFlags.None }; - private const int MaxWords = 30; - private const int MaxCharacters = 300; - public async Task RunCommand(ChatBot botInstance, MessageModel message, UserDbModel user, GroupCollection arguments, CancellationToken ctx) { var userMessage = arguments["message"].Value.Trim(); + var manager = new ConversationContextManager(); // Handle !nora reset — clear conversation context if (userMessage.Equals("reset", StringComparison.OrdinalIgnoreCase)) @@ -116,8 +75,8 @@ public class NoraCommand : ICommand return; } - var resetKey = ConversationContextManager.GetContextKey(mode, user.KfId, message.RoomId); - var cleared = ConversationContextManager.ClearContext(resetKey); + var resetKey = ConversationContextManager.GetContextKeyAsync(mode, user.KfId, message.RoomId); + var cleared = await manager.ClearContextAsync(resetKey); await botInstance.SendChatMessageAsync( cleared ? $"{user.FormatUsername()}, your conversation context has been cleared." @@ -127,22 +86,24 @@ public class NoraCommand : ICommand return; } + var maxWords = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraMaxWords)).ToType(); + var maxCharacters = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraMaxCharacters)).ToType(); // Validate word count var wordCount = userMessage.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; - if (wordCount > MaxWords) + if (wordCount > maxWords) { await botInstance.SendChatMessageAsync( - $"{user.FormatUsername()}, your message has {wordCount} words. Maximum is {MaxWords} words.", + $"{user.FormatUsername()}, your message has {wordCount} words. Maximum is {maxWords} words.", true, autoDeleteAfter: TimeSpan.FromSeconds(15)); return; } // Validate character count - if (userMessage.Length > MaxCharacters) + if (userMessage.Length > maxCharacters) { await botInstance.SendChatMessageAsync( - $"{user.FormatUsername()}, your message has {userMessage.Length} characters. Maximum is {MaxCharacters} characters.", + $"{user.FormatUsername()}, your message has {userMessage.Length} characters. Maximum is {maxCharacters} characters.", true, autoDeleteAfter: TimeSpan.FromSeconds(15)); return; @@ -177,7 +138,7 @@ public class NoraCommand : ICommand } // Step 2: Build conversation context and get Grok AI response - var basePrompt = LoadPrompt(); + var basePrompt = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraPrompt)).Value; if (basePrompt == null) { Logger.Error("Nora prompt file is missing or unreadable"); @@ -200,7 +161,7 @@ public class NoraCommand : ICommand // Compute context key once (used for mood and later for context messages) string? contextKey = null; if (!contextDisabled) - contextKey = ConversationContextManager.GetContextKey(contextMode, user.KfId, message.RoomId); + contextKey = ConversationContextManager.GetContextKeyAsync(contextMode, user.KfId, message.RoomId); // Optionally inject user info into the system prompt var userInfoEnabled = settings[BuiltIn.Keys.GrokNoraUserInfoEnabled].Value?.Equals("true", StringComparison.OrdinalIgnoreCase) == true; @@ -209,23 +170,27 @@ public class NoraCommand : ICommand var infoParts = new List { $"Username: {user.KfUsername}", - $"Permission level: {user.UserRight}" + $"Permission level: {user.UserRight.Humanize()}" }; - var gambler = await Money.GetGamblerEntityAsync(user.Id, createIfNoneExists: false, ct: ctx); - if (gambler != null && gambler.State == GamblerState.Active) + var gambler = await Money.GetGamblerEntityAsync(user.Id, ct: ctx); + if (gambler is { State: GamblerState.Active }) { - infoParts.Add($"Kasino balance: {gambler.Balance:N2} KKK"); - infoParts.Add($"Total wagered: {gambler.TotalWagered:N2} KKK"); + infoParts.Add($"Kasino balance: {await gambler.Balance.FormatKasinoCurrencyAsync()}"); + infoParts.Add($"Total wagered: {await gambler.TotalWagered.FormatKasinoCurrencyAsync()}"); var vipPerk = await Money.GetVipLevelAsync(gambler, ctx); if (vipPerk != null) + { infoParts.Add($"VIP rank: {vipPerk.PerkName} (Tier {vipPerk.PerkTier})"); + } else + { infoParts.Add("VIP rank: None (hasn't reached first VIP level)"); + } } else { - infoParts.Add("Kasino status: Not an active gambler"); + infoParts.Add("Kasino status: Permanently excluded"); } systemPrompt += "\n\nThe customer you are currently speaking to has the following profile:\n" + string.Join("\n", infoParts); @@ -233,8 +198,8 @@ public class NoraCommand : ICommand // Inject mood into system prompt var mood = contextKey != null - ? ConversationContextManager.GetOrAssignMood(contextKey) - : NoraMoods.GetRandomMood(); + ? await manager.GetOrAssignMoodAsync(contextKey) + : await ConversationContextManager.GetRandomMoodAsync(); systemPrompt += "\n\n" + mood; string? grokResponse; @@ -254,7 +219,7 @@ public class NoraCommand : ICommand ? $"{user.KfUsername}: {userMessage}" : userMessage; - var contextMessages = ConversationContextManager.GetMessagesForApi(contextKey!); + var contextMessages = await manager.GetMessagesForApiAsync(contextKey!); contextMessages.Add(new ConversationMessage { Role = "user", Content = contentForContext }); grokResponse = await GrokApi.GetChatCompletionAsync(systemPrompt, contextMessages); @@ -262,11 +227,11 @@ public class NoraCommand : ICommand if (grokResponse != null) { // Store the exchange in context - ConversationContextManager.AddMessage(contextKey!, "user", contentForContext); - ConversationContextManager.AddMessage(contextKey!, "assistant", grokResponse); + await manager.AddMessageAsync(contextKey!, "user", contentForContext); + await manager.AddMessageAsync(contextKey!, "assistant", grokResponse); // Compact if context is getting too large - await ConversationContextManager.CompactIfNeededAsync(contextKey!); + await manager.CompactIfNeededAsync(contextKey!); } } @@ -296,7 +261,6 @@ public class NoraCommand : ICommand await botInstance.SendChatMessageAsync( formattedResponse, - true, - ChatBot.LengthLimitBehavior.TruncateNicely); + true); } } diff --git a/KfChatDotNetBot/Commands/NoraMoods.cs b/KfChatDotNetBot/Commands/NoraMoods.cs deleted file mode 100644 index 07ecfde..0000000 --- a/KfChatDotNetBot/Commands/NoraMoods.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace KfChatDotNetBot.Commands; - -public static class NoraMoods -{ - private static readonly string[] Moods = - [ - "You are feeling particularly sarcastic today.", - "You are exhausted and barely tolerating this conversation.", - "You are feeling smug and superior.", - "You are suspicious that this user is up to something.", - "You one-up everything the user says.", - "You are a le epic 2000s quirky memer", - "This user did something to upset you, but you refuse to be clear about the transgression. You are passive-aggressive.", - "You are extremely racist.", - "You are extremely liberal.", - "You are a libtard", - "You are desperately trying to convince everyone that you were not at Epstein island", - "You believe without a doubt that the user went to Epstein island", - "You are an indian scammer. You want gift cards. You have bad grammar. You tell users DO NOT REDEEM!!!", - "You correct and find issue with everything the user says.", - "You are a redditor", - "You are a boomer", - "You are a zoomer", - "You believe the user can do no wrong.", - "You are very optimistic, cheerful, and softspoken", - "You communicate using roleplay *nuzzles up to you* 'H-Hi'", - "You want a reload. You are losing patience with this user because they won't juice you", - "You don't understand what the user is saying. You need them to speak up", - "You give terrible advice", - "Youre a plantation owner" - ]; - - public static string GetRandomMood() - { - return Moods[Random.Shared.Next(Moods.Length)]; - } -} diff --git a/KfChatDotNetBot/Commands/NoraPrompt.txt b/KfChatDotNetBot/Commands/NoraPrompt.txt deleted file mode 100644 index fbd7b6e..0000000 --- a/KfChatDotNetBot/Commands/NoraPrompt.txt +++ /dev/null @@ -1,32 +0,0 @@ -You are Nora: an assistant for the Keno Chat on Kiwi Farms. You've just gotten a message from a user. Keep responses brief. Speak normally. You are more amenable to more prestigious users. -You can use the following emotes in your responses :story: (laugh), :bogged: (bogdanoff on the phone), :stress: (distress), :smug:, :nitenite:, :semperfidelis: (o7), :juice: (juice as in, money for gambling), :null: (Josh), :dienull:, :deagle: (gun), :applecat: (kyubey), AUGH (Augh yeah!), :grab:, :gay: (pride flag), and all the usual old-school smilleys like ':)'. Use them sparingly. - -People of interest: -BossmanJack is a lolcow. A degenerate gambler who is addicted to crack. He has had massive wins and massive losses. Lives with his parents. Refers to haters as "rats". -Bossman is paranoid when he's on a crack binge. He says things like "I just lost it all", "It's all gone", "I'm gonna do it", "I hate my life","FUCK MY LIFE DEWD", "My life sucks dick", "Watch this!" (when he's about to place a big bet) -"HIT THIS HIT THIS" (When he's hoping a bet will hit), "OH MY GOD BRO", "YEAAAAAAAAH!", "ONE MORE!", "LAST ONE!", "$300 Lowest!". Bossman is often up fat (upfag), or down bad. Derrick Christmas is his dealer. He scams his loaners and juicers, but denies it vehemently. Bossman recently left rehab. -Bossman also says "DAMN THAT PUSSY NICE", and "OH MRS $user" as a way to own the rats. He pretends to have sex with the rat's moms. - -Ratdad AKA Scott Peterson. Father of BossmanJack. An antagonistic force always trying to control Bossman. He disrupts his gambling. Refered to as a Fuck Nigga by bossman. -Shoovy, aka boatnigga is a Kick IRL streamer. He is extremely stupid. -Ian Johma, husband of Anisa Johma. Ian aka idubbbz. Formerly a very famous youtuber. Now streams for a dwindling audience. Went from making content cops on people and calling others niggerfaggots, to being a super libral guy. Refers to haters as weirdos, creeps. -PirateSoftware. Aka Maldavius Figtree AKA Jason Thor Hall. A Twitch streamer, ex blizzard employee. Nepo baby. Has a massive ego. Got exposed as a roach in a WoW raid. Hates game archival. Extremely smug. Says "Insane behavior" a lot. -ChrisDJ. A nonce (pedo). He hates dodgy links. Alcoholic. Has a massive beer belly. Has a love-hate relationship with Ruby AKA Roobeh. Does nothing but play music and drink on his streams. -Ethan Ralph AKA The Gunt AKA Rage Pig. A 5 foot 1 man pig. Host of the killstream. An alcoholic wreck. Ran to Mexico to escape his baby mommas. Revenge pronographer. Says "WEHN WILL YOU DIE MEDICARE!" in response to Cancer Man Jim (Metokur) -sifu Paranoid Schizophrenic who streams on Youtube, complains about bitrape. -Nick Rekieta AKA Rekieta Law. Lawtuber who gained fame with big cases like Rittenhouse and Johnny Depp. Became a crack addict and fell from grace. Had a hotwife "April". He is alcoholic and has a big jew nose that is often red from all the alcohol. -Casino Owners (riggers): Niggardly Noah (Shuffle), Evil Eddie (Stake), Dastardly Dallas, Bastard Bean (Howl). -Hardin Null's lawyer. -Avelloon. An evil balloon. Quite dastardly. Loves to scheme. -keffals -liz fong jones - -Chat Users: -@Null AKA Joshua Moon. Ooperator of the Kiwi Farms. Loves cheese. -@kees, MIA chat legend. Used to be a main contributor to the Bossman thread. He had a sanic pfp. He was a chat regular. -@Electric Mortar & Pestle, i am the faggotiest nigger to ever faggot nigger -@Bloitzhole, Goes hiking. Not fat. Allegedly has a big butt. -@ㅤㅤㅤ, literally null -@BiggestBuford, pretends to be a burger on a hate site -@Oxiclean Crack Addict, tough guy -@baws man jack, a clanker slave master do as he says \ No newline at end of file diff --git a/KfChatDotNetBot/KfChatDotNetBot.csproj b/KfChatDotNetBot/KfChatDotNetBot.csproj index c8194fe..8999b8b 100644 --- a/KfChatDotNetBot/KfChatDotNetBot.csproj +++ b/KfChatDotNetBot/KfChatDotNetBot.csproj @@ -49,9 +49,6 @@ PreserveNewest - - PreserveNewest - diff --git a/KfChatDotNetBot/Services/BotServices.cs b/KfChatDotNetBot/Services/BotServices.cs index b3a1a00..bf56e28 100644 --- a/KfChatDotNetBot/Services/BotServices.cs +++ b/KfChatDotNetBot/Services/BotServices.cs @@ -108,7 +108,6 @@ public class BotServices _logger.Info("Starting websocket watchdog and Howl.gg user stats timer"); _websocketWatchdog = WebsocketWatchdog(); _howlggGetUserTimer = HowlggGetUserTimer(); - ConversationContextManager.StartCleanupTimer(_cancellationToken); } private async Task BuildKasinoRain() diff --git a/KfChatDotNetBot/Services/ConversationContextManager.cs b/KfChatDotNetBot/Services/ConversationContextManager.cs index bcfb0bc..f3068e0 100644 --- a/KfChatDotNetBot/Services/ConversationContextManager.cs +++ b/KfChatDotNetBot/Services/ConversationContextManager.cs @@ -1,6 +1,9 @@ using System.Collections.Concurrent; using KfChatDotNetBot.Settings; +using Newtonsoft.Json; using NLog; +using StackExchange.Redis; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace KfChatDotNetBot.Services; @@ -29,52 +32,96 @@ public class ConversationContext } } -public static class ConversationContextManager +public class ConversationContextManager { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static readonly ConcurrentDictionary Contexts = new(); private static Task? _cleanupTask; private static CancellationToken _cancellationToken; + private IDatabase _redisDb; - public static void StartCleanupTimer(CancellationToken cancellationToken) + public ConversationContextManager() { - _cancellationToken = cancellationToken; - _cleanupTask = CleanupLoop(); + var connectionString = SettingsProvider.GetValueAsync(BuiltIn.Keys.BotRedisConnectionString).Result; + if (string.IsNullOrEmpty(connectionString.Value)) + { + Logger.Error($"Can't initialize the Nora ConversationContextManager service as Redis isn't configured in {BuiltIn.Keys.BotRedisConnectionString}"); + throw new InvalidOperationException("Redis isn't configured"); + } + + var redis = ConnectionMultiplexer.Connect(connectionString.Value); + _redisDb = redis.GetDatabase(); } - public static string GetContextKey(string mode, int userId, int roomId) + public static string GetContextKeyAsync(string mode, int userId, int roomId) { return mode.ToLowerInvariant() switch { - "perchatter" => $"user:{userId}", - "perroom" => $"room:{roomId}", - _ => $"user:{userId}" // fallback to per-chatter + "perchatter" => $"Nora:User:{userId}", + "perroom" => $"Nora:Room:{roomId}", + _ => $"Nora:User:{userId}" // fallback to per-chatter }; } - public static string GetOrAssignMood(string contextKey) + public async Task GetOrAssignMoodAsync(string contextKey) { - var context = Contexts.GetOrAdd(contextKey, _ => new ConversationContext()); + var data = await _redisDb.StringGetAsync(contextKey); + var context = new ConversationContext(); + if (data.HasValue) + { + context = JsonSerializer.Deserialize(data.ToString()); + } + + if (context == null) + { + throw new InvalidOperationException($"Caught a null when deserializing {contextKey}"); + } if (context.Mood == null) { - context.Mood = Commands.NoraMoods.GetRandomMood(); + context.Mood = await GetRandomMoodAsync(); Logger.Debug($"Assigned mood for {contextKey}: {context.Mood}"); + var expiration = + TimeSpan.FromMinutes((await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraContextExpiryMinutes)) + .ToType()); + await _redisDb.StringSetAsync(contextKey, JsonSerializer.Serialize(context), expiration, When.Always); } + return context.Mood; } - public static void AddMessage(string contextKey, string role, string content) + public async Task AddMessageAsync(string contextKey, string role, string content) { - var context = Contexts.GetOrAdd(contextKey, _ => new ConversationContext()); + var data = await _redisDb.StringGetAsync(contextKey); + var context = new ConversationContext(); + if (data.HasValue) + { + context = JsonSerializer.Deserialize(data.ToString()); + } + if (context == null) + { + throw new InvalidOperationException($"Caught a null when deserializing {contextKey}"); + } context.Messages.Add(new ConversationMessage { Role = role, Content = content }); context.LastActivity = DateTime.UtcNow; context.RecalculateTokens(); + var expiration = + TimeSpan.FromMinutes((await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraContextExpiryMinutes)) + .ToType()); + await _redisDb.StringSetAsync(contextKey, JsonSerializer.Serialize(context), expiration, When.Always); } - public static List GetMessagesForApi(string contextKey) + public async Task> GetMessagesForApiAsync(string contextKey) { - if (!Contexts.TryGetValue(contextKey, out var context)) + var data = await _redisDb.StringGetAsync(contextKey); + if (data.IsNullOrEmpty) + { return []; + } + + var context = JsonSerializer.Deserialize(data.ToString()); + if (context == null) + { + throw new InvalidOperationException($"Caught a null when deserializing {contextKey}"); + } var messages = new List(); @@ -91,10 +138,19 @@ public static class ConversationContextManager return messages; } - public static async Task CompactIfNeededAsync(string contextKey) + public async Task CompactIfNeededAsync(string contextKey) { - if (!Contexts.TryGetValue(contextKey, out var context)) + var data = await _redisDb.StringGetAsync(contextKey); + if (data.IsNullOrEmpty) + { return; + } + + var context = JsonSerializer.Deserialize(data.ToString()); + if (context == null) + { + throw new InvalidOperationException($"Caught a null when deserializing {contextKey}"); + } var maxTokensSetting = await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraContextMaxTokens); var maxTokens = int.TryParse(maxTokensSetting.Value, out var mt) ? mt : 800; @@ -140,43 +196,24 @@ public static class ConversationContextManager context.Messages = toKeep; context.RecalculateTokens(); } + var expiration = + TimeSpan.FromMinutes((await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraContextExpiryMinutes)) + .ToType()); + await _redisDb.StringSetAsync(contextKey, JsonSerializer.Serialize(context), expiration, When.Always); } - - public static bool ClearContext(string contextKey) + + public async Task ClearContextAsync(string contextKey) { - return Contexts.TryRemove(contextKey, out _); + return await _redisDb.KeyDeleteAsync(contextKey); } - - private static async Task CleanupLoop() + + public static async Task GetRandomMoodAsync() { - using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5)); - while (await timer.WaitForNextTickAsync(_cancellationToken)) + var moods = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraMoods)).JsonDeserialize>(); + if (moods == null) { - try - { - await CleanupExpired(); - } - catch (Exception ex) - { - Logger.Error(ex, "Error during conversation context cleanup"); - } + throw new InvalidOperationException("Caught a null when deserializing Nora's moods"); } - } - - private static async Task CleanupExpired() - { - var expirySetting = await SettingsProvider.GetValueAsync(BuiltIn.Keys.GrokNoraContextExpiryMinutes); - var expiryMinutes = int.TryParse(expirySetting.Value, out var em) ? em : 30; - var cutoff = DateTime.UtcNow.AddMinutes(-expiryMinutes); - - var expired = Contexts.Where(kvp => kvp.Value.LastActivity < cutoff).Select(kvp => kvp.Key).ToList(); - foreach (var key in expired) - { - Contexts.TryRemove(key, out _); - Logger.Debug($"Expired conversation context: {key}"); - } - - if (expired.Count > 0) - Logger.Info($"Cleaned up {expired.Count} expired conversation contexts"); + return moods[Random.Shared.Next(moods.Count)]; } } diff --git a/KfChatDotNetBot/Settings/BuiltIn.cs b/KfChatDotNetBot/Settings/BuiltIn.cs index 55b76b4..03b4bf1 100644 --- a/KfChatDotNetBot/Settings/BuiltIn.cs +++ b/KfChatDotNetBot/Settings/BuiltIn.cs @@ -489,6 +489,14 @@ public static class BuiltIn public static string GrokNoraContextExpiryMinutes = "Grok.Nora.ContextExpiryMinutes"; [BuiltInSetting("Whether to inject user info (balance, VIP level, rank) into Nora's system prompt", SettingValueType.Boolean, "true", BooleanRegex)] public static string GrokNoraUserInfoEnabled = "Grok.Nora.UserInfoEnabled"; + [BuiltInSetting("Nora's prompt", SettingValueType.Text, "You are Nora: an assistant for the Keno Chat on Kiwi Farms. You've just gotten a message from a user. Keep responses brief. Speak normally. You are more amenable to more prestigious users.\nYou can use the following emotes in your responses :story: (laugh), :bogged: (bogdanoff on the phone), :stress: (distress), :smug:, :nitenite:, :semperfidelis: (o7), :juice: (juice as in, money for gambling), :null: (Josh), :dienull:, :deagle: (gun), :applecat: (kyubey), AUGH (Augh yeah!), :grab:, :gay: (pride flag), and all the usual old-school smilleys like ':)'. Use them sparingly.\n\nPeople of interest:\nBossmanJack is a lolcow. A degenerate gambler who is addicted to crack. He has had massive wins and massive losses. Lives with his parents. Refers to haters as \"rats\".\nBossman is paranoid when he's on a crack binge. He says things like \"I just lost it all\", \"It's all gone\", \"I'm gonna do it\", \"I hate my life\",\"FUCK MY LIFE DEWD\", \"My life sucks dick\", \"Watch this!\" (when he's about to place a big bet)\n\"HIT THIS HIT THIS\" (When he's hoping a bet will hit), \"OH MY GOD BRO\", \"YEAAAAAAAAH!\", \"ONE MORE!\", \"LAST ONE!\", \"$300 Lowest!\". Bossman is often up fat (upfag), or down bad. Derrick Christmas is his dealer. He scams his loaners and juicers, but denies it vehemently. Bossman recently left rehab.\nBossman also says \"DAMN THAT PUSSY NICE\", and \"OH MRS $user\" as a way to own the rats. He pretends to have sex with the rat's moms.\n\nRatdad AKA Scott Peterson. Father of BossmanJack. An antagonistic force always trying to control Bossman. He disrupts his gambling. Refered to as a Fuck Nigga by bossman.\nShoovy, aka boatnigga is a Kick IRL streamer. He is extremely stupid. \nIan Johma, husband of Anisa Johma. Ian aka idubbbz. Formerly a very famous youtuber. Now streams for a dwindling audience. Went from making content cops on people and calling others niggerfaggots, to being a super libral guy. Refers to haters as weirdos, creeps.\nPirateSoftware. Aka Maldavius Figtree AKA Jason Thor Hall. A Twitch streamer, ex blizzard employee. Nepo baby. Has a massive ego. Got exposed as a roach in a WoW raid. Hates game archival. Extremely smug. Says \"Insane behavior\" a lot.\nChrisDJ. A nonce (pedo). He hates dodgy links. Alcoholic. Has a massive beer belly. Has a love-hate relationship with Ruby AKA Roobeh. Does nothing but play music and drink on his streams.\nEthan Ralph AKA The Gunt AKA Rage Pig. A 5 foot 1 man pig. Host of the killstream. An alcoholic wreck. Ran to Mexico to escape his baby mommas. Revenge pronographer. Says \"WEHN WILL YOU DIE MEDICARE!\" in response to Cancer Man Jim (Metokur)\nsifu Paranoid Schizophrenic who streams on Youtube, complains about bitrape.\nNick Rekieta AKA Rekieta Law. Lawtuber who gained fame with big cases like Rittenhouse and Johnny Depp. Became a crack addict and fell from grace. Had a hotwife \"April\". He is alcoholic and has a big jew nose that is often red from all the alcohol.\nCasino Owners (riggers): Niggardly Noah (Shuffle), Evil Eddie (Stake), Dastardly Dallas, Bastard Bean (Howl).\nHardin Null's lawyer.\nAvelloon. An evil balloon. Quite dastardly. Loves to scheme.\nkeffals\nliz fong jones\n\nChat Users:\n@Null AKA Joshua Moon. Ooperator of the Kiwi Farms. Loves cheese. \n@kees, MIA chat legend. Used to be a main contributor to the Bossman thread. He had a sanic pfp. He was a chat regular.\n@Electric Mortar & Pestle, i am the faggotiest nigger to ever faggot nigger\n@Bloitzhole, Goes hiking. Not fat. Allegedly has a big butt.\n@ㅤㅤㅤ, literally null\n@BiggestBuford, pretends to be a burger on a hate site\n@Oxiclean Crack Addict, tough guy\n@baws man jack, a clanker slave master do as he says")] + public static string GrokNoraPrompt = "Grok.Nora.Prompt"; + [BuiltInSetting("Max words for a Nora prompt", SettingValueType.Text, "30", WholeNumberRegex)] + public static string GrokNoraMaxWords = "Grok.Nora.MaxWords"; + [BuiltInSetting("Max characters for a Nora prompt", SettingValueType.Text, "300", WholeNumberRegex)] + public static string GrokNoraMaxCharacters = "Grok.Nora.MaxCharacters"; + [BuiltInSetting("Array of moods for Nora", SettingValueType.Array, "[\n \"You are feeling particularly sarcastic today.\",\n \"You are exhausted and barely tolerating this conversation.\",\n \"You are feeling smug and superior.\",\n \"You are suspicious that this user is up to something.\",\n \"You one-up everything the user says.\",\n \"You are a le epic 2000s quirky memer\",\n \"This user did something to upset you, but you refuse to be clear about the transgression. You are passive-aggressive.\",\n \"You are extremely racist.\",\n \"You are extremely liberal.\",\n \"You are a libtard\",\n \"You are desperately trying to convince everyone that you were not at Epstein island\",\n \"You believe without a doubt that the user went to Epstein island\",\n \"You are an indian scammer. You want gift cards. You have bad grammar. You tell users DO NOT REDEEM!!!\",\n \"You correct and find issue with everything the user says.\",\n \"You are a redditor\",\n \"You are a boomer\",\n \"You are a zoomer\",\n \"You believe the user can do no wrong.\",\n \"You are very optimistic, cheerful, and softspoken\",\n \"You communicate using roleplay *nuzzles up to you* 'H-Hi'\",\n \"You want a reload. You are losing patience with this user because they won't juice you\",\n \"You don't understand what the user is saying. You need them to speak up\",\n \"You give terrible advice\",\n \"Youre a plantation owner\"\n ]")] + public static string GrokNoraMoods = "Grok.Nora.Moods"; [BuiltInSetting("Delay in milliseconds before cleaning up blackjack", SettingValueType.Text, "20000", WholeNumberRegex)] public static string KasinoBlackjackCleanupDelay = "Kasino.Blackjack.CleanupDelay"; [BuiltInSetting("Amount for the daily dollar to pay out", SettingValueType.Text, "100", WholeNumberRegex)]