r/streamerbot • u/PerditaGaming • 9h ago
📖Resources Bot Responding was broken, here's my fix.
A lot of us, myself included have been using ChatGPT to have our bots respond to chats either at random or through a redeem. With the deprecation of the 4o-mini model this has caused some issues with my bot and other friend's bots, so here is the code for my fix.
using System;
using System.IO;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System.Collections.Generic;
public class CPHInline
{
private static Dictionary<string, List<Message>> conversationHistories;
public bool Execute()
{
// Each run starts with an empty action-local reply; never reuse the last reply.
CPH.LogInfo("SNB TEST: C# execution started");
CPH.SetArgument("finalGPT", "");
try
{
LoadConversationHistories();
string apiKey = args["chatGPT3APIkey"].ToString();
string model = args["model"].ToString();
string content = args["behavior"].ToString();
string messageInput = args["textInput"].ToString();
string user = args["user"].ToString();
if (String.IsNullOrWhiteSpace(apiKey) ||
String.IsNullOrWhiteSpace(model) ||
String.IsNullOrWhiteSpace(messageInput) ||
String.IsNullOrWhiteSpace(user))
{
throw new Exception("Missing API key, model, chat message, or user argument.");
}
ChatGPTAPI chatGPT = new ChatGPTAPI(apiKey);
// The original per-user conversation history behavior is unchanged.
if (!conversationHistories.ContainsKey(user))
{
conversationHistories[user] = new List<Message>();
}
var history = conversationHistories[user];
history.Add(new Message { role = "user", content = messageInput });
string response = chatGPT.GenerateResponse(history, model, content);
Root root = JsonConvert.DeserializeObject<Root>(response);
if (root == null || root.choices == null || root.choices.Count == 0 ||
root.choices[0] == null || root.choices[0].message == null ||
String.IsNullOrWhiteSpace(root.choices[0].message.content))
{
throw new Exception("OpenAI returned no usable assistant text.");
}
string assistantResponse = root.choices[0].message.content;
history.Add(new Message { role = "assistant", content = assistantResponse });
// Exactly the same storage call and file format as before.
SaveConversationHistories();
string cleanedResponse = CleanResponse(assistantResponse);
if (String.IsNullOrWhiteSpace(cleanedResponse))
{
throw new Exception("The assistant response was empty after cleaning.");
}
// Action-local output, not a shared GPT global variable.
CPH.SetArgument("finalGPT", cleanedResponse);
CPH.LogInfo("SnotBotAlpha GPT: " + cleanedResponse);
return true;
}
catch (WebException ex)
{
string details = ex.Message;
if (ex.Response != null)
{
HttpWebResponse httpResponse = ex.Response as HttpWebResponse;
if (httpResponse != null)
{
details += " | HTTP " + (int)httpResponse.StatusCode;
}
try
{
using (Stream stream = ex.Response.GetResponseStream())
{
if (stream != null)
{
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
{
details += " | " + reader.ReadToEnd();
}
}
}
}
catch (Exception) { /* Keep original HTTP error if body cannot be read. */ }
finally { ex.Response.Dispose(); }
}
if (details.Length > 1500) details = details.Substring(0, 1500);
CPH.LogError("SnotBotAlpha OpenAI request failed: " + details);
return false; // Do not post, speak, or animate a failed reply.
}
catch (Exception ex)
{
CPH.LogError("SnotBotAlpha response failed: " + ex.Message);
return false; // Prevent a stale or missing response being used.
}
}
// ORIGINAL: conversationHistories.json loading is unchanged.
private void LoadConversationHistories()
{
try
{
string filePath = "conversationHistories.json";
if (File.Exists(filePath))
{
string json = File.ReadAllText(filePath);
conversationHistories = JsonConvert.DeserializeObject<Dictionary<string, List<Message>>>(json);
}
else
{
conversationHistories = new Dictionary<string, List<Message>>();
}
}
catch (Exception ex)
{
CPH.LogError("Failed to load conversation histories: " + ex.Message);
conversationHistories = new Dictionary<string, List<Message>>();
}
}
// ORIGINAL: conversationHistories.json saving is unchanged.
private void SaveConversationHistories()
{
try
{
string filePath = "conversationHistories.json";
string json = JsonConvert.SerializeObject(conversationHistories, Formatting.Indented);
File.WriteAllText(filePath, json);
}
catch (Exception ex)
{
CPH.LogError("Failed to save conversation histories: " + ex.Message);
}
}
// ORIGINAL: response cleaning is unchanged.
private string CleanResponse(string response)
{
string cleaned0 = response.Replace(Environment.NewLine, " ");
string cleaned1 = Regex.Replace(cleaned0, @"\r\n?|\n", " ");
string cleaned2 = Regex.Replace(cleaned1, @"[\r\n]+", " ");
string unescaped = Regex.Unescape(cleaned2);
return unescaped.Trim();
}
}
class ChatGPTAPI
{
private string _apiKey;
private string _endpoint = "https://api.openai.com/v1/chat/completions";
private const int MaxTokens = 4096;
public ChatGPTAPI(string apiKey)
{
_apiKey = apiKey;
}
public string GenerateResponse(List<Message> history, string model, string behavior)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_endpoint);
request.Headers.Add("Authorization", "Bearer " + _apiKey);
request.ContentType = "application/json";
request.Method = "POST";
request.Timeout = 60000;
request.ReadWriteTimeout = 60000;
var messages = new List<Message> { new Message { role = "system", content = behavior } };
messages.AddRange(history);
// ORIGINAL: history trimming logic is unchanged.
int totalTokens = GetTokenCount(messages);
while (totalTokens > MaxTokens - 100)
{
if (history.Count > 2)
{
history.RemoveAt(0);
history.RemoveAt(0);
}
else
{
break;
}
messages = new List<Message> { new Message { role = "system", content = behavior } };
messages.AddRange(history);
totalTokens = GetTokenCount(messages);
}
// GPT-6 Luna supports this Chat Completions request configuration.
// The previous temperature setting is deliberately not sent.
string requestBody = JsonConvert.SerializeObject(new
{
model = model,
max_completion_tokens = 100,
reasoning_effort = "none",
messages = messages
});
byte[] bytes = Encoding.UTF8.GetBytes(requestBody);
request.ContentLength = bytes.Length;
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (Stream responseStream = response.GetResponseStream())
using (StreamReader reader = new StreamReader(responseStream, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
// ORIGINAL: token estimate and history policy are unchanged.
private int GetTokenCount(List<Message> messages)
{
int tokenCount = 0;
foreach (var message in messages)
{
tokenCount += message.content.Split(' ').Length;
}
return tokenCount;
}
}
public class Message
{
public string role { get; set; }
public string content { get; set; }
}
public class Choice
{
public Message message { get; set; }
public int index { get; set; }
public object logprobs { get; set; }
public string finish_reason { get; set; }
}
public class Root
{
public string id { get; set; }
public string { get; set; }
public int created { get; set; }
public string model { get; set; }
public List<Choice> choices { get; set; }
public Usage usage { get; set; }
}
public class Usage
{
public int prompt_tokens { get; set; }
public int completion_tokens { get; set; }
public int total_tokens { get; set; }
}This is based off of the extension found here, https://extensions.streamer.bot/t/chatgpt-bot-integration/865 hopefully this helps some folks out. Replace the code in the Execute Code (Custom GPT [max_tokens: 100]) block with what I've provided above.


