mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
fix(markdown): require paired || delimiters for spoiler detection (#26105)
* fix(markdown): require paired || delimiters for spoiler detection An unpaired || (odd count across all inline tokens) would open a spoiler that never closes, causing closeRemainingStyles to extend it to the end of the text. This made all content after an unpaired || appear as hidden/spoiler in Telegram. Pre-count || delimiters across the entire inline token group and skip spoiler injection entirely when the count is less than 2 or odd. This prevents single | characters and unpaired || from triggering spoiler formatting. Closes #26068 Co-authored-by: Cursor <cursoragent@cursor.com> * fix: preserve valid spoiler pairs with trailing unmatched delimiters (#26105) (thanks @Sid-Qin) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Model fallback: keep explicit text + image fallback chains reachable even when `agents.defaults.models` allowlists are present, prefer explicit run `agentId` over session-key parsing for followup fallback override resolution (with session-key fallback), treat agent-level fallback overrides as configured in embedded runner preflight, and classify `model_cooldown` / `cooling down` errors as `rate_limit` so failover continues. (#11972, #24137, #17231)
|
||||
- Followups/Routing: when explicit origin routing fails, allow same-channel fallback dispatch (while still blocking cross-channel fallback) so followup replies do not get dropped on transient origin-adapter failures. (#26109) Thanks @Sid-Qin.
|
||||
- Agents/Model fallback: continue fallback traversal on unrecognized errors when candidates remain, while still throwing the original unknown error on the last candidate. (#26106) Thanks @Sid-Qin.
|
||||
- Telegram/Markdown spoilers: keep valid `||spoiler||` pairs while leaving unmatched trailing `||` delimiters as literal text, avoiding false all-or-nothing spoiler suppression. (#26105) Thanks @Sid-Qin.
|
||||
|
||||
## 2026.2.24
|
||||
|
||||
|
||||
@@ -144,8 +144,31 @@ function applySpoilerTokens(tokens: MarkdownToken[]): void {
|
||||
}
|
||||
|
||||
function injectSpoilersIntoInline(tokens: MarkdownToken[]): MarkdownToken[] {
|
||||
let totalDelims = 0;
|
||||
for (const token of tokens) {
|
||||
if (token.type !== "text") {
|
||||
continue;
|
||||
}
|
||||
const content = token.content ?? "";
|
||||
let i = 0;
|
||||
while (i < content.length) {
|
||||
const next = content.indexOf("||", i);
|
||||
if (next === -1) {
|
||||
break;
|
||||
}
|
||||
totalDelims += 1;
|
||||
i = next + 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalDelims < 2) {
|
||||
return tokens;
|
||||
}
|
||||
const usableDelims = totalDelims - (totalDelims % 2);
|
||||
|
||||
const result: MarkdownToken[] = [];
|
||||
const state = { spoilerOpen: false };
|
||||
let consumedDelims = 0;
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token.type !== "text") {
|
||||
@@ -168,9 +191,14 @@ function injectSpoilersIntoInline(tokens: MarkdownToken[]): MarkdownToken[] {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (consumedDelims >= usableDelims) {
|
||||
result.push(createTextToken(token, content.slice(index)));
|
||||
break;
|
||||
}
|
||||
if (next > index) {
|
||||
result.push(createTextToken(token, content.slice(index, next)));
|
||||
}
|
||||
consumedDelims += 1;
|
||||
state.spoilerOpen = !state.spoilerOpen;
|
||||
result.push({
|
||||
type: state.spoilerOpen ? "spoiler_open" : "spoiler_close",
|
||||
|
||||
@@ -94,4 +94,22 @@ describe("markdownToTelegramHtml", () => {
|
||||
const res = markdownToTelegramHtml("||**secret** text||");
|
||||
expect(res).toBe("<tg-spoiler><b>secret</b> text</tg-spoiler>");
|
||||
});
|
||||
|
||||
it("does not treat single pipe as spoiler", () => {
|
||||
const res = markdownToTelegramHtml("( ̄_ ̄|) face");
|
||||
expect(res).not.toContain("tg-spoiler");
|
||||
expect(res).toContain("|");
|
||||
});
|
||||
|
||||
it("does not treat unpaired || as spoiler", () => {
|
||||
const res = markdownToTelegramHtml("before || after");
|
||||
expect(res).not.toContain("tg-spoiler");
|
||||
expect(res).toContain("||");
|
||||
});
|
||||
|
||||
it("keeps valid spoiler pairs when a trailing || is unmatched", () => {
|
||||
const res = markdownToTelegramHtml("||secret|| trailing ||");
|
||||
expect(res).toContain("<tg-spoiler>secret</tg-spoiler>");
|
||||
expect(res).toContain("trailing ||");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user