From f9b2f4d7cb348fdadefff2b54dea2b755abc2207 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Fri, 25 Sep 2026 10:50:12 +0000 Subject: [PATCH] Keep every line of a multi-line message in chat history The text log has a message's line breaks in it, but history was read one line at a time, so every line after the first failed to parse and was dropped. A line that starts no entry of its own now continues the message before it, which also recovers what earlier versions wrote. Co-Authored-By: Claude Opus 5.5 --- .../com/ts3client/chatlog/ChatLogStore.java | 41 ++++++++++++++----- .../ts3client/chatlog/ChatLogStoreTest.java | 12 ++++++ 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java b/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java index 63230fa..ec8ddb8 100644 --- a/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java +++ b/ts3-client/core/src/main/java/com/ts3client/chatlog/ChatLogStore.java @@ -175,13 +175,33 @@ public final class ChatLogStore { return entries; } + /** + * A message is written as it was sent, so one with line breaks spans several lines of the + * log: a line that starts no entry of its own continues the message before it. + */ private static List readTextMessages(LogFiles files, ChatLogScope scope) throws IOException { if (!Files.isRegularFile(files.txt)) return Collections.emptyList(); List entries = new ArrayList<>(); + Matcher message = null; + StringBuilder text = new StringBuilder(); for (String line : Files.readAllLines(files.txt, StandardCharsets.UTF_8)) { - String entry = textLineToHtml(line, scope); - if (entry != null) entries.add(entry); + Matcher m = TEXT_MESSAGE.matcher(line); + boolean begin = line.startsWith("*** "); + if (!m.matches() && !begin) { + if (message != null) text.append('\n').append(line); + continue; + } + if (message != null) entries.add(messageLineHtml(message, text, scope)); + message = null; + if (begin) { + entries.add(beginLineHtml(line)); + } else { + message = m; + text.setLength(0); + text.append(m.group(3)); + } } + if (message != null) entries.add(messageLineHtml(message, text, scope)); return entries; } @@ -190,14 +210,12 @@ public final class ChatLogStore { return new ArrayList<>(entries.subList(entries.size() - limit, entries.size())); } - private static String textLineToHtml(String line, ChatLogScope scope) { - if (line == null || line.isBlank()) return null; - if (line.startsWith("*** ")) { - return "
" - + BBCode.escape(line) + "
"; - } - Matcher m = TEXT_MESSAGE.matcher(line); - if (!m.matches()) return null; + private static String beginLineHtml(String line) { + return "
" + + BBCode.escape(line) + "
"; + } + + private static String messageLineHtml(Matcher m, CharSequence text, ChatLogScope scope) { String target = switch (scope) { case PRIVATE -> "Private"; case CHANNEL -> "Channel"; @@ -206,7 +224,8 @@ public final class ChatLogStore { return "
" + "<" + m.group(1) + "> " + """ + BBCode.escape(m.group(2)) + "": " - + "" + BBCode.toHtml(m.group(3)) + "
"; + + "" + BBCode.toHtml(text.toString().stripTrailing()) + + ""; } private static String beginHtml(String kind, LocalDateTime timestamp) { diff --git a/ts3-client/core/src/test/java/com/ts3client/chatlog/ChatLogStoreTest.java b/ts3-client/core/src/test/java/com/ts3client/chatlog/ChatLogStoreTest.java index 9ce690e..260b22b 100644 --- a/ts3-client/core/src/test/java/com/ts3client/chatlog/ChatLogStoreTest.java +++ b/ts3-client/core/src/test/java/com/ts3client/chatlog/ChatLogStoreTest.java @@ -68,4 +68,16 @@ class ChatLogStoreTest { assertTrue(capped.truncated()); assertTrue(capped.entries().get(9).contains("filler " + (ChatLogStore.HISTORY_LIMIT - 1))); } + + @Test + void readsAMessageWithLineBreaksBackWhole() throws Exception { + ChatLogStore logs = new ChatLogStore(temp, "+Tyg2JtxE8vRNZp+JiUBnmBh0MY="); + logs.appendMessage(ChatLogScope.CHANNEL, false, 5, "uid", "alice", "first line\n\nthird line"); + logs.appendMessage(ChatLogScope.CHANNEL, false, 5, "uid", "bob", "reply"); + + List history = logs.channelHtmlHistory(); + assertEquals(2, history.size()); + assertTrue(history.get(0).contains("first line

third line"), history.get(0)); + assertTrue(history.get(1).contains("reply")); + } }