Channels
Plugin Telegram Latex Sanitizer
OpenClaw plugin: sanitize LaTeX delimiters in Telegram outbound messages. Addresses openclaw/openclaw#78934.
Install
npm install
npm
README
# @rhinocap/openclaw-plugin-telegram-latex-sanitizer
OpenClaw plugin that rewrites raw LaTeX in outbound Telegram messages to plain
Unicode/text equivalents, so models that emit `\frac{a}{b}` or `\(\alpha\)` don't
leak unrendered LaTeX into chat.
Addresses upstream issue
[openclaw/openclaw#78934](https://github.com/openclaw/openclaw/issues/78934).
## How it works
The plugin registers a `message_sending` hook (see
[plugin hooks](https://github.com/openclaw/openclaw/blob/main/docs/plugins/hooks.md)),
filters by `ctx.channelId === "telegram"`, and rewrites `event.content`. If
nothing changes it returns `undefined` so other plugins can still chain.
It only touches outbound text. Inbound messages, attachments, captions, and
non-Telegram channels are untouched.
## Install
```bash
openclaw plugins install clawhub:@rhinocap/openclaw-plugin-telegram-latex-sanitizer
```
(or `openclaw plugins install @rhinocap/openclaw-plugin-telegram-latex-sanitizer`
to resolve from npm directly.)
No configuration required — it activates on startup and applies to every
outbound Telegram message.
## Supported conversions
### Symbols
| LaTeX | Output |
| --- | --- |
| `\rightarrow`, `\to` | `→` |
| `\leftarrow` | `←` |
| `\Rightarrow`, `\implies` | `⇒` |
| `\Leftarrow` | `⇐` |
| `\leftrightarrow` | `↔` |
| `\Leftrightarrow`, `\iff` | `⇔` |
| `\uparrow` / `\downarrow` | `↑` / `↓` |
| `\mapsto` | `↦` |
| `\geq`, `\ge` | `≥` |
| `\leq`, `\le` | `≤` |
| `\neq`, `\ne` | `≠` |
| `\approx` | `≈` |
| `\equiv` | `≡` |
| `\sim` / `\propto` | `∼` / `∝` |
| `\times` | `×` |
| `\cdot` | `·` |
| `\pm` / `\mp` | `±` / `∓` |
| `\div` | `÷` |
| `\ast` / `\star` / `\circ` / `\bullet` | `∗` / `⋆` / `∘` / `•` |
| `\infty` | `∞` |
| `\partial` | `∂` |
| `\nabla` | `∇` |
| `\sum` / `\prod` / `\coprod` | `Σ` / `∏` / `∐` |
| `\int` / `\iint` / `\iiint` / `\oint` | `∫` / `∬` / `∭` / `∮` |
| `\forall` / `\exists` / `\nexists` | `∀` / `∃` / `∄` |
| `\in` / `\notin` / `\ni` | `∈` / `∉` / `∋` |
| `\subset` / `\supset` / `\subseteq` / `\supseteq` | `⊂` / `⊃` / `⊆` / `⊇` |
| `\cup` / `\cap` | `∪` / `∩` |
| `\emptyset`, `\varnothing` | `∅` |
| `\setminus` | `∖` |
| `\land` / `\lor` / `\lnot`, `\neg` | `∧` / `∨` / `¬` |
| `\ldots`, `\dots` / `\cdots` / `\vdots` / `\ddots` | `…` / `⋯` / `⋮` / `⋱` |
| `\langle` / `\rangle` | `⟨` / `⟩` |
| `\lceil` / `\rceil` / `\lfloor` / `\rfloor` | `⌈` / `⌉` / `⌊` / `⌋` |
| `\perp` / `\parallel` / `\angle` / `\triangle` | `⊥` / `∥` / `∠` / `△` |
| `\hbar` / `\ell` / `\Re` / `\Im` / `\aleph` | `ℏ` / `ℓ` / `ℜ` / `ℑ` / `ℵ` |
### Greek letters
Lowercase: `\alpha \beta \gamma \delta \epsilon \zeta \eta \theta \iota \kappa
\lambda \mu \nu \xi \pi \rho \sigma \tau \upsilon \phi \chi \psi \omega` (plus
`\varepsilon \vartheta \varpi \varrho \varsigma \varphi`).
Uppercase: `\Gamma \Delta \Theta \Lambda \Xi \Pi \Sigma \Upsilon \Phi \Psi
\Omega`.
### Delimiters and structure
| LaTeX | Output |
| --- | --- |
| `\(...\)` | `...` (wrapper stripped) |
| `\[...\]` | `...` (wrapper stripped) |
| `$$...$$` | `...` (wrapper stripped) |
| `$...$` | `...` (only when the inner content contains `\`, `^`, or `_` — see [limits](#known-limits)) |
| `\frac{a}{b}` | `(a)/(b)` |
| `\sqrt{x}` | `√(x)` |
| `^{x}` | `^(x)` |
| `_{x}` | `_(x)` |
| `\unknown{foo}` | `foo` (unknown brace command — backslash and braces stripped) |
| `\unknownbare` | `unknownbare` (unknown bare command — backslash stripped) |
Nested structures are handled recursively. For example:
- `\frac{\frac{1}{2}}{3}` → `((1)/(2))/(3)`
- `\sum_{i=1}^{n} i = \frac{n(n+1)}{2}` → `Σ_(i=1)^(n) i = (n(n+1))/(2)`
- `$$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$` → `x = (-b ± √(b^2 - 4ac))/(2a)`
## Known limits
This plugin is a pragmatic regex-based sanitizer, not a full LaTeX parser:
1. **Single-`$` inline math is conservative.** A `$...$` pair is only stripped
when the inner content contains `\`, `^`, or `_`. This avoids false
positives on prose dollar amounts (`Coffee is $5 today`, `$5 and $10 dollars`).
The trade-off is that pure-symbol single-`$` math like `$x + y$` is left
wrapped — wrap it in `\(...\)` or `$$...$$` if you want it stripped.
2. **Symbol coverage is a curated list, not exhaustive.** Roughly 80+ of the
most common LaTeX symbols and Greek letters are mapped. Anything else falls
through the unknown-command path: `\foo{bar}` becomes `bar` and `\foo`
becomes `foo`. Open an issue if you hit a symbol that should be mapped.
3. **No semantic understanding of macros.** `\href{url}` becomes `url`,
`\ref{eq:1}` becomes `eq:1`, etc. Good enough for chat readability;
not a substitute for proper rendering.
4. **Escaped backslashes (`\\`) round-trip unchanged.** A literal `\\` in the
source stays as `\\` in the output (it is not interpreted as a LaTeX
newline).
5. **No nested-brace parsing for symbols.** Inside `\frac{...}{...}` and
`\sqrt{...}`, the inner braces must balance trivially — nested braces are
resolved iteratively (innermost first), but extreme depth past 50
iterations is bailed out of.
## Development
```bash
npm install
npm test # vitest, no openclaw runtime needed for the sanitizer tests
npm run build # tsc to dist/
```
The pure transformation lives in `src/sanitizer.ts` and is exported as
`sanitizeLatex(text)` and `containsLatex(text)` for direct use without the
plugin host.
## Compatibility
| Field | Value |
| --- | --- |
| `pluginApi` | `>=2026.3.24-beta.2` |
| Node | `>=22` |
| Peer dependency | `openclaw >=2026.3.24-beta.2` |
These pin the floor based on the version that introduced the
`message_sending` hook plus typed `replyToId`/`threadId` routing fields. If a
newer OpenClaw release tightens the SDK in a breaking way, this version range
will be tightened in a patch release.
## Issues / contributions
Issues and PRs welcome at
[github.com/rhinocap/openclaw-plugin-telegram-latex-sanitizer](https://github.com/rhinocap/openclaw-plugin-telegram-latex-sanitizer).
For Telegram LaTeX leakage reports specifically, please file here rather than
upstream openclaw — that's the contract from
[openclaw/openclaw#78934](https://github.com/openclaw/openclaw/issues/78934).
## License
MIT © Andrew Cunliffe
channels
Comments
Sign in to leave a comment