聊天室 MD 格式完整教程(v3.8)
本教程详细讲解聊天室(Lobby)支持的 v3 扩展语法,包括标准 Markdown 和聊天室扩展组件。 所有示例均可直接复制到聊天室测试。 语法版本 v3.8(含 v4 宏系统):服务端 PHP 解析器 + 前端 JS 渲染双路径一致,所有组件由服务端解析为结构化 blocks,前端渲染。 v3.8 代码块保护:fenced 代码块(三反引号)与内联代码(单反引号)内的自定义语法只显示不解析(宏定义/触发写在代码块里同样不生效)。 v3.8 转义:
\&→ 字面&(动作串内防拆分)、\|→ 字面|(参数值内)。 v3.7 内容换行:组件内容文本中\n转真实换行(\\n转义保留字面);定时组件未绑定且 end 含发送:不自动启动。 v3.6 防刷屏:倒计时/计时器必须绑定=1启动;动作链未绑定且含发送:不自动启动。
目录
一、标准 Markdown 语法
聊天室使用 marked(GFM 模式)渲染标准 Markdown。
# 一级标题
**粗体文字** *斜体文字* ~~删除线文字~~
> 引用文字 `行内代码`
- 无序列表 1. 有序列表
- [x] 已完成 - [ ] 未完成
[点击访问](https://example.com)
| 列1 | 列2 |
|-----|-----|
| a | b |⚠️ 消息内禁止图片(防流量攻击);图片仅在弹窗内容里允许(png/jpg/gif/webp/bmp/svg/ico)。 所有自定义语法写进 fenced 代码块或
`内联代码时只显示不解析(见第二章),宏也不会注册。
1.1 回复引用(聊天室通用)
回复消息时引用块自动处理:
- 引用文本过长:超过 60 字自动截断 + 省略号(
...) - 引用 MD/特殊格式消息:显示「特殊格式不支持预览」(不解析组件语法)
- 引用表情消息:显示表情图片
二、扩展语法基础结构
v3 所有扩展组件统一格式:
[!类型:内容|键=值|键=值|...]| 部分 | 说明 | 必填 |
|---|---|---|
[! | 组件标记(普通 [...] 文本不解析) | 是 |
类型 | 组件类型(中文,如 文本/按钮/投票/画板),[! 后紧跟,到 :/` | /]`/空白为止 |
内容 | : 后的第一个无键段 | 是 |
键=值 | 命名参数,` | 分隔,顺序任意(键为小写字母/中文开头,含字母数字中文._-`) |
| 无键段 | 不含 = 的段按内容处理(如按钮附加文字、画板图形) | 否 |
组件格式规则(v3.8):
- 嵌套:内容可含子组件,
[]深度感知闭合。如[!按钮:开|发送:[!文本:你好]] - 未闭合(缺
]):按普通文本显示并提示错误,不按组件渲染 |是参数分隔符,参数值内的字面|用\|转义- 代码块保护:fenced 代码块与内联代码内所有自定义语法只显示不解析
- 转义:
\&→ 字面&、\|→ 字面| - 状态(输入/开关/变量)仅当前消息内有效
- 单条消息长度上限 4000 字符
- 所有展示文本自动 HTML 转义(防注入)
[!文本:你好] ← 最简
[!输入:占位符|id=name] ← 带命名参数
[!按钮:开始|发送:开打!] ← 动作按钮
[!倒计时:90|id=t|绑定=1|end=发送:时间到!] ← 组合参数
[!按钮:开始倒计时|倒计时:t]
[!按钮:开启[!文本:你好]] ← 嵌套子组件三、组件总览
| # | 类型 | 用途 | 章节 |
|---|---|---|---|
| 1 | 文本 | 文本框 | 4 |
| 2 | 画板 | 像素画板 | 13 |
| 3 | 表格 | 数据表 | 4 |
| 4 | 音乐 | 音乐播放 | 7 |
| 5 | 图集 | 图片轮播 | 7 |
| 6 | 投票 | 投票统计 | 5 |
| 7 | 进度 | 进度条 | 4 |
| 8 | 倒计时 | 倒计时 | 8 |
| 9 | 计时 | 秒表 | 8 |
| 10 | 定时 | 定时触发 | 8 |
| 11 | 按钮 | 动作按钮 | 5 |
| 12 | 发送 | 发送消息按钮 | 5 |
| 13 | 复制 | 复制按钮 | 5 |
| 14 | 弹窗 | 弹窗 | 5 |
| 15 | 确认 | 确认按钮 | 5 |
| 16 | 输入 | 输入框 | 5 |
| 17 | 显示 | 变量值显示 | 6 |
| 18 | 开关 | 多选切换 | 5 |
| 19 | 隐藏 | 隐藏组件 | 6 |
| 20 | 变量 | 变量赋值 | 6 |
| 21 | 条件 | 条件分支 | 6 |
| 22 | 动作链 | 多步编排 | 13 |
| 23 | 宏 | 宏定义 | 14 |
| 24 | 触发宏 | 宏触发 | 14 |
| 25 | 宏删 | 宏删除 | 14 |
| 26 | 宏列表 | 宏库列表 | 14 |
通用参数(大部分组件可用):
id=名(统一标识,动作链定位)、x=像素、y=像素(弹窗内位置偏移)、隐藏=1(初始隐藏)、颜色=#hex(前景色)。
四、展示组件
| 组件 | 语法 | 说明 |
|---|---|---|
| 文本 | [!文本:内容|#f00|大|居中] | 彩色文本;大/小 显式档位;无显式按长度自适应(≤4字特大、≤12大、≤40中、>40小);标题=/t= 标题行、bg=色/-1 背景 |
| 进度 | [!进度:7/10|颜色=#f00|id=p1] | 进度条(内容 当前/上限 或 70%);支持 bar.set:/bar.add:/bar.sub: 实时更新 |
| 表格 | [!表格:列1/列2|数据...|标题=表名] | 单元格以 / 分隔,行以 | 分隔;支持 %值% 变量引用,自动换行 |
| 图集 | [!图集:标题|URL1|URL2|...|自动=秒|id=名] | 图集轮播弹窗;图片 URL 需 http(s)+扩展名白名单;自动= 秒数自动轮播,点击放大预览 |
| 音乐 | [!音乐:URL|标题=歌名|自动播放|id=名|状态=变量名] | 音乐播放器(带播放/暂停/进度条;房间点歌系统同步;状态= 输出播放/暂停/结束到变量) |
| 投票 | [!投票:问题|选项1|选项2|多选=2|perm=@名|洗牌|id=名|mode=bar] | 投票(多选/权限/洗牌/实时统计条) |
| 倒计时 | [!倒计时:90|id=名|绑定=1|end=动作|lock=组|bar=进度条id] | 倒计时(必须绑定启动(v3.6 防刷屏)/锁定按钮组/进度联动);动作 倒计时:名/倒计时:名|重置 |
| 计时 | [!计时:25分|id=名|绑定=1|end=动作|重复=1] | 正向计时器(必须绑定启动(v3.6 防刷屏)/到点动作/自动重复) |
| 定时 | [!定时:09:00|动作|重复=1|绑定=1|id=名] | 定时到点执行动作(绑定=1 自动启动;未绑定且动作含 发送: 不自动启动(v3.7 防刷屏),需按钮 定时:名 手动启动) |
[!文本:🎉 欢迎|#f00|大|居中]
[!进度:7/10|id=血条]
[!倒计时:5分|id=茶|绑定=1|end=发送:茶泡好了!]
[!按钮:泡茶|倒计时:茶]
[!投票:今晚吃什么|火锅|烧烤|日料|多选=2]
[!表格:商品/价格|苹果/5元|香蕉/3元|标题=小卖部]五、交互组件
| 组件 | 语法 | 说明 |
|---|---|---|
| 按钮 | [!按钮:文字|动作|颜色=#f00|perm=@小红|click=限制|sound=音效|anim=动画] | 动作按钮(动作见第九章);perm= 仅指定用户可点;click= 点击限制 |
| 发送 | [!发送:文字|内容] | 点击把内容填入输入框并发送 |
| 复制 | [!复制:文字|内容] | 点击复制到剪贴板 |
| 弹窗 | [!弹窗:按钮文字|弹窗内容|标题=标题] | 弹窗(内容可含组件/图片,弹窗内图片全放行;弹窗内组件可用 x=/y= 定位) |
| 确认 | [!确认:提示语|要发送的内容] | 点击先弹确认框,确认后再执行动作 |
| 输入 | [!输入:占位符|id=名|ok=答案1/答案2|on=动作] | 输入框,值存入 {名};回车匹配 ok= 才执行 on=,不写 ok= 任意输入触发 |
| 显示 | [!显示:名|颜色=#f00] | 实时显示输入/开关/变量值(随变量刷新重绘) |
| 开关 | [!开关:值1/值2|id=名|颜色=1|锁定=1|on=动作] | 点击循环切换值,当前值存入 {名} 并联动动作 |
| 隐藏 | [!隐藏:名] | 隐藏指定 id 组件(动作 隐藏:名 可再显示) |
[!输入:输入昵称|id=昵称] [!显示:昵称]
[!开关:开/关|id=灯|on=发送:灯被切到{灯}]
[!确认:确定删除?|发送:已删除]
[!复制:copy码|INVITE-2026]
[!弹窗:📖 说明|**这是弹窗**,支持图片与嵌套按钮|标题=帮助]六、数据与逻辑组件
| 组件 | 语法 | 说明 |
|---|---|---|
| 变量 | [!变量:名=值] | 定义变量(隐藏,{名} 引用),值支持数学表达式;赋值后同消息内立即生效并刷新 |
| 条件 | [!条件:表达式|成立动作|否则动作] | 表达成立执行成立动作,否则执行否则动作(可省略) |
| 动作链 | [!动作链:步骤/步骤|循环=N|id=名|绑定=1] | 隐形编排器(见第十三章) |
条件表达式格式:
| 语法 | 含义 |
|---|---|
{变量} | 非空即真 |
{变量}=值 / == | 相等 |
!= | 不等 |
> >= < <= | 数值比较 |
{变量}~a/b/c | 列表包含 |
1 | 恒真 |
[!变量:金币=100] 我的金币:[!显示:金币]
[!变量:血量=100-20] 血量:[!显示:血量]
[!条件:{金币}>50|💰 有钱人|发送:继续努力]
[!隐藏:标题]七、媒体组件
7.1 音乐
[!音乐:音频URL|标题=歌名|自动播放|id=m1|状态=mus]- 音频 URL:mp3 等标准音频链接(白名单域名校验)
自动播放自动开始;状态=初始状态(mus 表示播放 / 其他暂停)- 带播放/暂停/进度条控制;房间点歌系统同步
- 动作:
音乐:play/音乐:stop
7.2 图集
见第四章「图集」。
八、时间组件
| 组件 | 语法 | 说明 |
|---|---|---|
| 倒计时 | [!倒计时:90|id=名|end=动作|绑定=1] | 内容为秒数或 1时30分15秒;end= 到时执行动作;绑定=1 隐藏本体仅作定时器;动作 倒计时:名 启动、倒计时:名|重置 重置 |
| 计时 | [!计时:id=sw1|开始=1] | 点击开始/暂停秒表;动作 计时:名 切换 |
| 定时 | [!定时:10|end=动作|id=名] | 到时间点执行 end=;未绑定且含 发送: 不自动启动(v3.7 防刷屏),需动作 定时:名 手动启动 |
[!倒计时:90|id=t1|绑定=1|end=发送:时间到!]
[!计时:id=sw1|开始=1]
[!定时:10|id=at1|绑定=1|end=发送:到点提醒!]
[!按钮:启动倒计时|倒计时:t1]九、动作系统
动作是按钮/开关/倒计时等组件的"执行体",统一 前缀:内容:
| 动作 | 说明 | 示例 |
|---|---|---|
发送:内容 | 填入输入框并发送 | 发送:大家好 |
复制:内容 | 复制到剪贴板 | 复制:https://... |
弹窗:内容 | 弹窗 | 弹窗:说明文字 |
跳转:URL | 新标签打开 | 跳转:https://x.com |
开关:名 | 切换开关组件 | 开关:sw1 |
显示:名 / 隐藏:名 | 显示/隐藏指定组件 | 显示:板1 |
倒计时:名 | 启动/重置倒计时 | 倒计时:t1 |
计时:名 | 启动/暂停秒表 | 计时:sw1 |
定时:名 | 手动启动定时器 | 定时:at1 |
变量:名=值 | 赋值变量 | 变量:金币=100 |
循环:N | 循环标记 | 循环:3 |
洗牌:名 | 重排选项 | 洗牌:v1 |
音乐:动作 | 控制音乐 | 音乐:play |
随机:名=最小,最大 | 随机整数写回 | 随机:骰=1,6 |
等待:秒 | 延时(支持小数) | 等待:1.5 |
分支:表达式 | 条件分支 | 分支:{金币}>=100 |
动作链:名 | 启动/控制动作链 | 动作链:c1 |
英文兼容(v2 历史消息):
send:copy:show:hide:switch:timer.start:timer.stop:incr:decr:reset:close:set:for:bar.add:bar.set:bar.sub:embed:modal:。
[!按钮:掷骰子|随机:骰=1,6&发送:你掷出了 {骰} 点]
[!按钮:抽奖|洗牌:名单,取1&弹窗:🎁 中奖者|{名单}]
[!按钮:翻页|变量:页=取余({页}+1,10)]
[!按钮:行动|分支:{选择}=1→发送:⚔️ 攻击;2→发送:🛡️ 防御;其他→发送:无效]十、条件动作与动作串
任意动作可带条件后缀,满足才执行:
动作?条件
动作?条件:否则动作?分隔动作与条件:?后必须是{开头才视为条件(防 URL 误判);:分隔否则动作- 条件支持:
{名}非空、= != > >= < <=、~(列表包含) - 成功/失败分支可嵌套条件动作(≤3 层)
&连接多个动作顺序执行(动作串);\&转义为字面&(URL 参数内);URL 查询串中的&不拆分,&后是动作前缀或新 URL 才拆分
[!开关:🦔|on=变量:分数={分数}+10?{洞1}=1:发送:打空了!]
[!按钮:签到|发送:签到成功&变量:天数={天数}+1?{已签}!=1:发送:今天已签过啦!]
[!按钮:攻击|随机:伤害=5,15&变量:血量={血量}-{伤害}?{血量}>10:发送:⚠️ 倒下了]
[!按钮:结算|变量:分数={分数}+10?{分数}>=100:变量:分数={分数}+5?{分数}>=60:发送:不及格]十一、值引用与数学
11.1 引用语法
{名} 或 {名|默认值}
%名% 或 %名|默认值%- 变量名:中文/字母/数字/下划线/短横线
|默认值:变量为空时显示默认值- 组件内容、动作、表格单元格、条件表达式、画板参数中均可引用
列表值用 / 或 , 分隔,在列表型参数中自动展开:
[!变量:菜单=火锅,烧烤,日料]
[!投票:今晚吃什么|{菜单}] ← 展开为 3 个选项
[!变量:图库=https://a.png,https://b.png]
[!图集:我的图库|{图库}] ← 展开为 2 张图11.2 数学表达式与函数
+ - * / ( ) 四则运算
取余({a},{b}) 取整({x}) 绝对值({x}) 最大({a},{b}) 最小({a},{b})白名单校验:仅数字
+ - * / ( ) 空格 %与上述函数可执行,防注入。
[!按钮:加5|变量:金币={金币}+5]
[!按钮:翻页|变量:页=取余({页}+1,10)]
[!按钮:保底|变量:伤害=最大(0,{攻击}-{防御})]十二、通用参数
12.1 颜色(裸色简写)
[!文本:你好|#f00] ← 红色(3 位/6 位 hex 均可)
[!按钮:确定|#0f0] ← 按钮也支持
[!文本:大号居中|#f00|大]12.2 统一 ID
所有组件支持 id=名:渲染时注入 data-ui-id,供动作链目标、显示:/隐藏: 按 id 操作。同一消息内唯一。
[!文本:标题|id=标题]
[!动作链:目标=标题;颜色=#f00;延时=300|循环]12.3 点击次数限制 click
| 写法 | 含义 |
|---|---|
click=*1 | 每人只能点 1 次 |
click=*5 | 每人独立 5 次 |
click=1 | 所有人合计只能点 1 次 |
click=@张三:2 | 仅张三 2 次,其他人无限 |
click=5@张三:2 | 全局 5 次 + 张三 2 次 |
[!按钮:签到|发送:签到成功|click=*1] ← 每人只能签到一次
[!按钮:抢到|发送:我抢到了!|click=1] ← 所有人只能抢一次12.4 弹窗内定位(x/y)
组件支持 x=数字 / y=数字 偏移(px,可负值),相对自身流位置定位,手机/电脑显示位置一致:
[!按钮:🌟|id=星|x=10|y=5]十三、画板与动作链
13.1 画板
[!画板[:尺寸]|图形序列|bg=色|grid=0|id=板1|text=..|modal=1|hide=1]- 尺寸:1~20(默认 20);
bg=背景色;grid=0隐藏网格 text=画板文字(tx/ty/ts/tc定位/字号/颜色)- 图形代码:
l线p折线c曲线r矩形o圆d点t三角h心形pt多边形dl菱形st星形fr边框框tx文字 - 图形间以
;分隔;描边色图形:坐标:#颜色、填充|f#颜色、线宽|wN - 坐标与画板尺寸同比例(0~尺寸-1),超界自动裁剪
- 图形参数支持
{名}引用,变量变时自动重绘;modal=1折叠为按钮点击展开;hide=1初始隐藏
[!画板:20|o:10,10,6|dl:10,10,5|st:10,10,3|id=卡牌]
[!画板:10|o:{圆x},5,2:{颜色}|grid=0]13.2 动作链(隐形编排器)
[!动作链:步骤1/步骤2|循环=N|id=名|绑定=1]
步骤 = 目标=组件id;操作;延时=秒;条件=表达式;跳转=步骤号(步骤内 `;` 分隔)- 双 ID:
目标=组件id操作对象;id=链名对外接口 - 触发:省略
绑定=自动;绑定=1等外部触发;v3.6 防刷屏:未绑定且含发送:步骤的不自动启动,必须绑定 - 操作集:
移动x/y缩放旋转(累积增量)透明闪烁颜色文字隐藏/显示状态变量音效更新(画板重绘)任意 v3 动作 - 循环在消息可见时才跑,划出视口暂停;
绑定=1不自动启动,用动作动作链:名启动 - 控制动作:
开始暂停继续停止重播
[!动作链:目标=标题;闪烁=3;延时=200|id=开场|绑定=1]
[!倒计时:5|id=开打计时|绑定=1|end=动作链:开场,开始&发送:开打!]
[!按钮:开始|倒计时:开打计时]
[!按钮:⏸ 暂停|动作链:跳动,暂停]13.2.1 动作链控制动作
动作链:名,开始 动作链:名,暂停 动作链:名,继续 动作链:名,停止 动作链:名,重播十四、宏系统(v4)
宏是玩家自定义的 MD 组件模板,聊天室房间级共享。每条宏有唯一 名称、可选的 昵称(触发别名)、参数列表 params 和 模板 template。同一消息内可以用 [!宏:...] 定义/[!宏删:...] 删除,用 [!触发宏:...] 展开调用;也可以在 BOT 面板 / 宏管理里通过 HTTP API 管理(见 HTTP 接口文档第 10 章)。
源码:宏解析与展开见 MarkdownMessageParser.php,存储见 MacroRepository.php。
14.1 定义宏 宏
[!宏:名称|昵称(参数1,参数2)=模板内容]- 名称:必填,唯一,
[A-Za-z0-9_中文]1~20 字符,不能与内置组件重名 - 昵称:可选,触发别名,全局唯一,
[A-Za-z0-9_中文.-]1~32 字符 - 参数:可选,逗号分隔 ≤8 个,模板内用
{参数名}占位 - 模板:任意合法 MD 语法(组件/动作链/变量/嵌套宏),≤500 字符
- 定义消息发送后注册到房间宏库(带创建者身份),并显示为宏卡片
- 代码块内的
[!宏:...]不注册(仅展示) - 每人最多注册 50 个宏;昵称唯一
[!宏:点歌卡|点歌(歌名,歌手)=[!音乐:{歌名}|标题={歌手}|自动播放]]
[!宏:早安|za(名)=[!文本:早上好,{名}!]]
[!宏:签到=[!文本:✅ 签到成功]&变量:签到次数={次数|0}+1]14.2 触发宏
[!触发宏:昵称] ← 无参
[!触发宏:昵称:值1|值2] ← 位置参数传值- 优先按昵称匹配,其次按名称匹配
- 参数按声明顺序填入
{参数名};缺失用空串 - 嵌套触发:模板内可内含
[!触发宏:],递归深度 ≤3(循环引用自动截断) - 未找到的宏:渲染为"宏不存在"提示
- 消息总长保护:展开结果超限自动截断
[!触发宏:点歌卡:One Ok Rock|Al To You]14.3 修改 / 删除
[!宏:名称|新昵称(参数)=新模板] ← 同名重定义即修改(仅创建者)
[!宏删:名称] ← 删除(仅创建者)14.4 宏列表
[!宏列表]以列表卡片展示房间宏库(最近 50 个:昵称/名称/参数/模板/创建者)。
十五、安全与限制
| 限制项 | 值 |
|---|---|
| 消息最大长度 | 4000 字符 |
| 解析节点数 | 超限整条降级为纯文本 |
| JSON 广播 | 64KB 上限(超限自动降级纯文本) |
| 宏模板 | ≤500 字符,每人 ≤50 个,昵称唯一 |
| 宏展开深度 | ≤3(防递归死循环) |
| 图片域名 | 消息内禁图片;弹窗内 http(s)+扩展名白名单 |
| XSS | 前端 escapeHtml + DOMPurify(style 仅颜色属性) |
| 动作串 URL | 仅拆分 & 后动作前缀/新 URL,参数内 & 保留 |
| 定时防刷屏 | 定时组件未绑定且含发送不自动启动 |
| 代码块 | 所有自定义语法在代码块内不解析不注册 |
十六、完整示例合集
# 🎲 单骰
[!按钮:🎲 掷 1d6|随机:骰=1,6&发送:🎲 你掷出了 {骰} 点]
# 🎲 双骰(自动求和)
[!变量:总=0]
[!按钮:🎲 掷 2d6|随机:骰1=1,6&随机:骰2=1,6&变量:总={骰1}+{骰2}&发送:🎲 {骰1}+{骰2}={总}]
# ✂️ 猜拳(电脑随机)
[!按钮:✌️ 剪刀|随机:电脑=1,3&发送:👍 平局?{电脑}=1:发送:💔 你输了?{电脑}=2:发送:🎉 你赢了]
# 📢 每人只能点一次
[!按钮:签到|发送:✅ 签到成功!|click=*1]
# 🔒 所有人只能点一次
[!按钮:抢沙发|发送:🧨 我是第一个!|click=1]
# 🎁 游戏结算
[!变量:奖励=取整(随机(1,10))*10]
[!显示:奖励|颜色=#f59e0b]
[!进度:{血}/100|颜色=#22c55e|id=血条]
[!按钮:领取|变量:金币={金币|0}+{奖励}&发送:我获得了{金币}金币]文档版本:v3.8(含 v4 宏系统)· 语法规范详见《MD扩展语法规范v3.md》(v3.8)。