From f0fc79c142e6d69f90b3b73d6066ecf88d566a90 Mon Sep 17 00:00:00 2001
From: barelyprofessional
<150058423+barelyprofessional@users.noreply.github.com>
Date: Sun, 9 Feb 2025 15:59:39 +0800
Subject: [PATCH] Attempt to improve the way long strings are split up. There's
a new extension method called FancySplitMessage to achieve this. Truncation
options now work on bytes instead of string length too
---
KfChatDotNetBot/ChatBot.cs | 9 +--
KfChatDotNetBot/Commands/AdminCommands.cs | 4 +-
KfChatDotNetBot/Extensions.cs | 79 ++++++++++++++++++++++-
3 files changed, 85 insertions(+), 7 deletions(-)
diff --git a/KfChatDotNetBot/ChatBot.cs b/KfChatDotNetBot/ChatBot.cs
index 0165a75..c1b82a4 100644
--- a/KfChatDotNetBot/ChatBot.cs
+++ b/KfChatDotNetBot/ChatBot.cs
@@ -285,7 +285,7 @@ public class ChatBot
return messageTracker;
}
- if (Encoding.UTF8.GetByteCount(messageTracker.Message) > lengthLimit && lengthLimitBehavior != LengthLimitBehavior.DoNothing)
+ if (messageTracker.Message.Utf8LengthBytes() > lengthLimit && lengthLimitBehavior != LengthLimitBehavior.DoNothing)
{
if (lengthLimitBehavior == LengthLimitBehavior.RefuseToSend)
{
@@ -296,7 +296,8 @@ public class ChatBot
}
if (lengthLimitBehavior == LengthLimitBehavior.TruncateNicely)
{
- messageTracker.Message = messageTracker.Message.Truncate(lengthLimit);
+ // '…' is 3 bytes so we have to make room for it
+ messageTracker.Message = messageTracker.Message.TruncateBytes(lengthLimit - 3).TrimEnd() + "…";
}
if (lengthLimitBehavior == LengthLimitBehavior.TruncateExactly)
@@ -304,7 +305,7 @@ public class ChatBot
// ReSharper disable once ReplaceSubstringWithRangeIndexer
// The range indexer is a fucking piece of shit that does not work.
// TrimEnd in case you end up truncating on a space (happened during testing) as Sneedchat will trim it
- messageTracker.Message = messageTracker.Message.Substring(0, lengthLimit).TrimEnd();
+ messageTracker.Message = messageTracker.Message.TruncateBytes(lengthLimit).TrimEnd();
}
}
@@ -316,7 +317,7 @@ public class ChatBot
}
public SentMessageTrackerModel SendChatMessage(string message, bool bypassSeshDetect = false,
- LengthLimitBehavior lengthLimitBehavior = LengthLimitBehavior.TruncateNicely, int lengthLimit = 500)
+ LengthLimitBehavior lengthLimitBehavior = LengthLimitBehavior.TruncateNicely, int lengthLimit = 1023)
{
return SendChatMessageAsync(message, bypassSeshDetect, lengthLimitBehavior, lengthLimit).Result;
}
diff --git a/KfChatDotNetBot/Commands/AdminCommands.cs b/KfChatDotNetBot/Commands/AdminCommands.cs
index 991c450..daae646 100644
--- a/KfChatDotNetBot/Commands/AdminCommands.cs
+++ b/KfChatDotNetBot/Commands/AdminCommands.cs
@@ -123,7 +123,7 @@ public class GmKasinoListCommand : ICommand
result += $"[br]{i}: {image}";
}
- await botInstance.SendChatMessagesAsync(result.SplitMessage().ToList(), true);
+ await botInstance.SendChatMessagesAsync(result.FancySplitMessage(), true);
}
}
@@ -213,7 +213,7 @@ public class GnKasinoListCommand : ICommand
result += $"[br]{i}: {image}";
}
- await botInstance.SendChatMessagesAsync(result.SplitMessage().ToList(), true);
+ await botInstance.SendChatMessagesAsync(result.FancySplitMessage(), true);
}
}
diff --git a/KfChatDotNetBot/Extensions.cs b/KfChatDotNetBot/Extensions.cs
index 27b507b..1b99dab 100644
--- a/KfChatDotNetBot/Extensions.cs
+++ b/KfChatDotNetBot/Extensions.cs
@@ -1,4 +1,5 @@
-using System.Text.RegularExpressions;
+using System.Text;
+using System.Text.RegularExpressions;
namespace KfChatDotNetBot;
@@ -44,4 +45,80 @@ public static class Extensions
}
}
+ ///
+ /// Split messages to x number of bytes while avoiding splitting mid-word where possible
+ ///
+ /// String that should get split
+ /// Length limit, no part should be > than the number of bytes specified
+ /// Limit for how many parts to return (returns first n elements). Set to 0 to disable.
+ ///
+ public static List FancySplitMessage(this string s, int partLengthBytes = 1023, int partLimit = 5)
+ {
+ var output = new List();
+ var part = string.Empty;
+ foreach (var word in s.Split(' '))
+ {
+ if (word.Utf8LengthBytes() > partLengthBytes)
+ {
+ // Add the part already in memory if there is one
+ if (part != string.Empty)
+ {
+ output.Add(part.TrimEnd());
+ part = string.Empty;
+ }
+ // Breaks into chunks of x size which will break really long URLs etc. but no other way really
+ output.AddRange(word.ChunkBytes(partLengthBytes));
+ continue;
+ }
+
+ if (part.Utf8LengthBytes() + word.Utf8LengthBytes() > partLengthBytes)
+ {
+ // TrimEnd() to remove trailing spaces
+ output.Add(part.TrimEnd());
+ part = word + " ";
+ continue;
+ }
+
+ part += word + " ";
+ }
+
+ // Add on whatever remains
+ if (part != string.Empty)
+ {
+ output.Add(part.TrimEnd());
+ }
+
+ if (partLimit != 0 && output.Count > partLimit)
+ {
+ return output.Take(partLimit).ToList();
+ }
+
+ return output;
+ }
+
+ public static int Utf8LengthBytes(this string s)
+ {
+ return Encoding.UTF8.GetByteCount(s);
+ }
+
+ public static IEnumerable ChunkBytes(this string input, int bytesPerChunk)
+ {
+ var bytes = Encoding.UTF8.GetBytes(input);
+
+ for (var i = 0; i < bytes.Length; i += bytesPerChunk)
+ {
+ var chunkSize = Math.Min(bytesPerChunk, bytes.Length - i);
+ yield return Encoding.UTF8.GetString(bytes, i, chunkSize);
+ }
+ }
+
+
+ public static string TruncateBytes(this string s, int limitBytes)
+ {
+ return Encoding.UTF8.GetString(
+ Encoding.UTF8.GetBytes(s)
+ .Take(limitBytes)
+ .ToArray()
+ ).TrimEnd();
+ }
}
\ No newline at end of file