Repository navigation
Regex Script
A Regex Script rewrites text at a specific point in the message pipeline: it matches a JavaScript regular expression against a string and replaces the match with an OUT template, which may itself contain Curly Brased Syntaxes and special @@ directives.
Scripts can be defined in three scopes, and all three are combined and run together, in this order, for every mode except edittrans:
- Preset scripts — edited under Settings → the "Preset" page's Regex Script accordion. These are saved and loaded as part of the active connection Preset and exported/imported with it, so they apply to every character while that preset is loaded, not to a single character.
- Character scripts — edited in the character's own config panel.
- Module scripts — on every currently enabled module, concatenated in module order.
For edittrans scripts only, the order is preset → module → character instead.
Note: Settings also has a separate "Global Regex" page. Despite the name, that list is not applied to any message; it is only a save slot used by that page's own Import/Export buttons. If you want a script to run everywhere, put it in the Preset's Regex Script list, not "Global Regex".
Within one scope, scripts run in list order top-to-bottom unless reordered with <order N> (see below).
Set per-script via the "Modification Type" dropdown. Each is a distinct point in the pipeline:
| Type (saved value) | UI label | Runs on | When |
|---|---|---|---|
editinput |
Modify Input | The text you type, before it is stored as a chat message | When you send a message |
editoutput |
Modify Output | The AI's streamed or generated reply, before it is stored as a chat message | After generation |
editprocess |
Modify Request Data | Each message's text as it's assembled into the outgoing request, and the character's first message/greeting used for token counting | While building the prompt |
editdisplay |
Modify Display | Stored message text only when rendered on screen; never written back to chat data | When the message is displayed |
edittrans |
Edit Translation Display | Text after auto-translation, before it is shown | After translation, via a separate, lighter engine (see below) |
disabled |
Disabled | Never; matches no mode, effectively turning the script off without deleting it | — |
Lua edit triggers (editInput/editOutput/editDisplay callbacks) run before the regex list, for editinput, editoutput, and editdisplay. editprocess skips this step entirely.
editdisplay additionally runs a "display" Trigger Script hook (non-group characters only), then any plugin hook registered for that mode, then a CBS ({{...}}) parsing pass, before the regex list itself runs.
edittrans scripts run through a separate, lighter engine instead of the main script pipeline. It supports the same flag/action syntax (<order N>, <cbs>, <move_top>, <move_bottom>) but not @@emo, @@inject, or @@repeat_back.
The Flag field is a normal JS regex flag string, restricted to d g i m s u v y. Unsupported characters are silently stripped, and duplicates are collapsed.
- If Custom Flag is off, the effective flags are always
g. - If it's on but the flag field ends up empty after all directive tokens are removed, it falls back to
u, notg.
The Flags editor also has toggle buttons for g i m u s, plus custom bracket directives (below).
Any <...> token in the Flag field is parsed out before the string is used as regex flags, and turned into either an ordering rule or an action:
-
<order N>— sets execution order. When any script in the whole combined list uses<order>, all scripts are re-sorted by order, highest first, before any of them run (scripts without<order>default to order0). -
<cbs>— evaluates the IN pattern itself through the CBS parser ({{...}}macros) before compiling the regex, so the pattern can depend on character/chat data. Without it, IN is used as a literal regex source. -
<no_end_nl>— suppresses the automatic trailing newline described below. -
<move_top>,<move_bottom>,<repeat_back>— alternate way to trigger the same-named@@directives (see next section) without needing the@@...prefix in OUT. -
<inject>— undocumented in the UI, but recognized identically to typing@@injectin OUT. - Any other bracket text is still stripped from the flags but has no built-in effect (harmless custom marker).
Multiple directives can be combined in one flag string, comma- or space-separated inside the brackets, e.g. flag: "gi<order 5><cbs>".
If OUT ends with > (e.g. a closing HTML tag) the engine appends a newline automatically, unless <no_end_nl> is set.
OUT is passed through $n → literal newline substitution first, then used as the replacement.
- Standard ECMAScript replacement patterns work:
$&(whole match),$1.."$9" (capture groups),$`/$'(before/after),$<name>(named group). These all work natively for a normal (non-@@) replacement. - After a normal replacement, the entire result is re-parsed by CBS again, so OUT can safely contain
{{...}}macros. - Inside
@@move_top/@@move_bottomoutput (see below), group substitution is reimplemented separately and only supports$0..$9and$&.$<name>for named groups does not currently work: it does not match against the actual named group, so named-group references are left as literal text in move directives. This holds for both the main script engine and the edittrans engine.
If OUT starts with @@, or the script has any bracket action (above), the script enters "directive mode": the regex is tested against the current text first, and only a recognized directive (or a plain fallback) runs.
| Directive | Syntax | Effect |
|---|---|---|
@@emo <name> |
OUT starts with @@emo (no flag equivalent) |
If IN matches, adds <name> to that character's recent emotion images (must exist among the character's configured emotion images), kept as a stack of up to 5, used by the emotion/sprite view. No-op for preview characters.
|
@@inject |
OUT starts with @@inject, or <inject> flag |
If IN matches and the script is running on an actual chat message, saves the current (pre-strip) text back into the chat log at that message's position, then removes the matched text from what's returned or displayed. Because it always targets the currently selected character, it has no effect for group members or preview characters. |
@@move_top <text> |
OUT starts with @@move_top, or <move_top> flag |
Removes the matched text from its original position, then re-inserts the replacement text at the very top. Only the first match moves — see the callout below. |
@@move_bottom <text> |
OUT starts with @@move_bottom, or <move_bottom> flag |
Same as above but appends at the bottom. |
@@repeat_back[ end|start|end_nl|start_nl] |
OUT starts with @@repeat_back, or <repeat_back> flag |
Only runs when IN does not match the current text. Walks backward through chat history to the nearest earlier message with the same role (falling back to the active greeting text if none found), matches IN against that text, and copies the match into the current text: appended (default/end), prepended (start), or with a newline separator (end_nl/start_nl). Requires the script to be running on an actual chat message. If that earlier text does not match IN, the script throws a TypeError instead of skipping cleanly; the error is caught and logged to the console by the script runner, and the chat text is left unchanged.
|
any other @@... text |
— | Not a recognized prefix and reaches no directive branch, so it is used as a literal replacement (same as a normal, non-@@ OUT), including the CBS re-parse pass. |
Global flag is ignored for move directives. Even with the Global (g) flag checked, @@move_top/@@move_bottom drop the g flag before matching, so only the first match is ever moved, never all matches. This is true in both the main script engine and the edittrans engine.
Results are cached per combination of the active scripts, the input text, the modification type, the chat, and the current CBS state, up to 1000 entries with the oldest evicted first. This means repeated renders of the same text skip re-running every script.
The same pipeline also runs a fuzzy-match pass over {{asset-type::name}}-style tags in the text against the character's/module's asset names (Dynamic Assets), but only for editoutput unconditionally, and for editdisplay only if the "Dynamic Assets on Display" setting is on. It never runs for editinput/editprocess. This is a separate feature layered into the same pipeline, not a Regex Script directive — see HTML Syntaxes / @ Syntaxes for the asset tag syntax itself.
- Import/Export of a script list produces
{"type":"regex","data":[...]}JSON; importing merges into the target list rather than replacing it.
Hide a status block, show HTML instead (Modify Display):
- IN:
\[status\] - OUT:
<div class="status-box">{{getvar::hp}}/100 HP</div> - Type: Modify Display, Flag: default (
g). - The AI is instructed (in its prompt) to output the literal text
[status]; this never touches chat storage, so the raw response stays cheap in tokens while the display shows rendered HTML. See HTML Syntaxes for what's allowed inside OUT.
Strip a scratchpad the model must not repeat, but still keep it saved (Modify Output, @@inject):
- IN:
<scratchpad>[\s\S]*?<\/scratchpad> - OUT:
@@inject - Type: Modify Output.
- The full text (including the scratchpad) is written into the stored message, but the scratchpad is stripped from what continues through the pipeline/display for that turn.
Move an out-of-band author's note to the end of the reply (Modify Output):
- IN:
\(OOC:.*?\) - OUT:
@@move_top $& - Type: Modify Output, flag
<order 10>if you need it to run before other scripts.
Getting started
- RisuAI Basics
- Creating a Basic Bot
- Coming from upstream RisuAI? Migrating from upstream
- What changed from upstream? Changes from upstream
Syntax
Scripting
Characters and prompts
Tools
Memory
Plugins
Settings