### 即时消息
该模块增加了向房间发送易于理解的信息的支持,同时也支持为房间本身关联可读性强的信息,如房间名称和话题。
#### 事件
{{% event event="m.room.message" desired_example_name="m.room.message$m.text" %}}
{{% event event="m.room.name" %}}
{{% event event="m.room.topic" %}}
{{% event event="m.room.avatar" %}}
{{% event event="m.room.pinned_events" %}}
##### m.room.message 消息类型(msgtype)
每个 [m.room.message](#mroommessage) 必须包含一个 `msgtype` 键,用于标识发送消息的类型。不同类型的消息有各自必须和可选的键,具体如下。如果客户端无法显示给定的 `msgtype`,那么应当显示备用的纯文本 `body` 字段。
某些消息类型支持事件内容中的 HTML,客户端应优先显示可用的 HTML。目前,`m.text`、`m.emote`、`m.notice`、`m.image`、`m.file`、`m.audio`、`m.video` 和 `m.key.verification.request` 支持额外的 `format` 参数 `org.matrix.custom.html`。当提供该字段时,必须同时提供携带 HTML 的 `formatted_body`。HTML 的纯文本版本则应存于 `body` 字段。
{{% boxes/note %}}
{{% changed-in v="1.10" %}}
在以往的规范版本中,`format` 和 `formatted` 字段仅限于 `m.text`、`m.emote`、`m.notice` 以及 `m.key.verification.request`。现在该列表扩展至 `m.image`、`m.file`、`m.audio`、`m.video` 以支持[媒体标题](#media-captions)。
{{% /boxes/note %}}
为防止跨站脚本攻击(XSS)、HTML 注入及类似攻击,客户端应限制渲染的 HTML 范围。强烈建议仅允许以下 HTML 标签,其余标签应拒绝使用与渲染:`del`、`h1`、`h2`、`h3`、`h4`、`h5`、`h6`、`blockquote`、`p`、`a`、`ul`、`ol`、`sup`、`sub`、`li`、`b`、`i`、`u`、`strong`、`em`、`s`、`code`、`hr`、`br`、`div`、`table`、`thead`、`tbody`、`tr`、`th`、`td`、`caption`、`pre`、`span`、`img`、`details`、`summary`。
{{% boxes/note %}}
{{% added-in v="1.10" %}}
当 HTML 功能在 [WHATWG HTML Living Standard](https://html.spec.whatwg.org/multipage/) 标准中被弃用时,可以无需提交 [规范变更提案](/proposals)而弃用并用其现代等价替换之。
{{% /boxes/note %}}
{{% boxes/note %}}
{{% changed-in v="1.10" %}}
在以往规范中,建议使用 `font` 标签及其 `data-mx-bg-color`、`data-mx-color` 和 `color` 属性。该标签现已弃用,新的消息推荐使用带有 `data-mx-bg-color` 和 `data-mx-color` 属性的 `span` 标签替代。
{{% /boxes/note %}}
上述标签的所有属性均不应被允许,因为部分属性可能带来其他干扰性风险,比如添加 `onclick` 事件或设置过大的文本。客户端仅应允许下表中为各标签列出的属性。其中,`data-mx-bg-color` 和 `data-mx-color` 为列表项时,客户端应将其值(即 `#` 开头的 6 位十六进制颜色代码)转换为该标签相应的 CSS/属性。
| 标签 | 允许的属性 |
|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `span` | `data-mx-bg-color`、`data-mx-color`、`data-mx-spoiler`(参见[剧透消息](#spoiler-messages))、`data-mx-maths`(参见[数学消息](#mathematical-messages)) |
| `a` | `target`、`href`(前提是值不是相对路径,且 scheme 为 `https`、`http`、`ftp`、`mailto`、`magnet` 中之一) |
| `img` | `width`、`height`、`alt`、`title`、`src`(前提是来源为 [Matrix 内容 (`mxc://`) URI](#matrix-content-mxc-uris)) |
| `ol` | `start` |
| `code` | `class`(仅允许以 `language-` 开头的 class,以便语法高亮) |
| `div` | `data-mx-maths`(参见[数学消息](#mathematical-messages)) |
除此之外,Web 客户端应确保*所有* `a` 标签获得 `rel="noopener"` 属性,以防目标页面获取当前客户端标签页/窗口的引用。
标签嵌套不得超过 100 层。客户端仅应支持其能够渲染的子集标签,对无法渲染的标签采用其他表现方式显示。例如,若客户端无法正确渲染表格,可回退为制表符分隔文本。
除了不渲染不安全的 HTML 外,客户端也不应在事件中生成不安全的 HTML。客户端同样不应生成不必要的 HTML,比如由于富文本编辑导致的多余的段落标签。事件中的 HTML 应为有效 HTML,例如有适当的闭合标签、正确的属性(结合本文档自定义说明),且整体结构合法。
{{% boxes/note %}}
{{% changed-in v="1.13" %}}
在更早的规范版本中,[富回复](#rich-replies) 可以使用特殊标签 `mx-reply`。现在不再需要这样做。客户端应去除该标签及其内容。详情请参见“富回复”章节。
{{% /boxes/note %}}
{{% boxes/note %}}
未来的规范会支持更强大且可扩展的消息格式化选项,例如提案 [MSC1767](https://github.com/matrix-org/matrix-spec-proposals/pull/1767)。
{{% /boxes/note %}}
{{% msgtypes %}}
#### 客户端行为
客户端应验证收到事件的结构,确保所需字段存在且类型正确。对于格式错误的事件,可以选择丢弃或向用户显示占位提示消息。被修订(redacted)的 `m.room.message` 事件必须从客户端删除,可以用占位文本(如“[REDACTED]”)替换,或直接从消息视图移除。
带有附件的事件(如 `m.image`、`m.file`)应使用[内容仓库模块](#content-repository)上传(如可用)。所得的 `mxc://` URI 可用于 `url` 字段。
客户端可通过 `info.thumbnail_url` 字段为附件带上客户端生成的缩略图。该缩略图也应为 `mxc://` URI。呈现附带附件的事件时,客户端可直接使用缩略图,或者通过[内容仓库模块](#content-repository)请求 homeserver 基于原始附件生成缩略图。
##### 发送消息时的推荐做法
在发送失败时,客户端应使用指数退避算法重试请求,重试时间 T 为一段时间,建议不超过 5 分钟。超时后客户端应停止重试,并将消息标记为“未发送”。用户应能够手动重新发送未发送消息。
用户可能会一次输入并快速发送多条消息。客户端应保持用户发送消息的顺序,这意味着应等待上一请求响应后再发送下一个请求。这可能导致“队头阻塞”。为减轻此影响,应按房间分别使用队列而非全局队列,因为顺序仅在单一房间内有意义,房间间无需严格顺序。
##### 本地回显(Local Echo)
用户点击“发送”按钮时,消息应立即在消息视图中显示,哪怕消息正在发送中。这一过程称为“本地回显”。客户端应实现本地消息回显。客户端可采用不同展示方式显示尚未被服务器处理的消息。当服务器响应后应移除该特殊格式。
客户端需要能将其发送的消息和从事件流中收到的同一消息进行匹配。从事件流收到的同一消息的回显称为“远程回显”。本地回显和远程回显都要能被识别为相同消息,以防止重复显示。理想情况下,这一过程对用户透明:UI 从本地回显切换为远程回显时不会闪烁。通过使用用于发送事件的事务 ID,可减少切换时的闪烁。事务 ID 会作为收到事件时 `unsigned` 数据中的 `transaction_id` 字段返回。
如果客户端无法使用事务 ID,那么当远程回显在消息发送请求完成*之前*到达事件流时,很可能会出现闪烁。在这种情况下,事件在消息发送请求完成、客户端获得事件 ID 之前就到了,导致无法将其识别为远程回显。这样客户端在一段时间内(取决于服务器响应速度)会同时显示两条消息。请求完成后,客户端可通过查找重复事件 ID 移除多余事件。
##### 计算用户的显示名
客户端可能希望在成员列表或消息发送时展示房间成员的可读型显示名。然而,不同成员可能出现显示名冲突。显示名在展示给用户前必须唯一化处理,以防止冒充其他用户。
为确保客户端间一致处理,推荐使用如下算法为指定用户计算唯一显示名:
1. 检查相关用户的 `m.room.member` 状态事件。
2. 若该状态事件无 `displayname` 字段或该字段为 `null`,则用其原始用户 ID 作为显示名。否则:
3. 若 `m.room.member` 事件中的 `displayname` 在房间中所有 `membership: join` 或 `membership: invite` 成员里是唯一的,则用该 `displayname` 作为可见显示名。否则:
4. 若 `displayname` 不唯一,应结合用户 ID 做唯一化处理,例如“显示名 (@id:homeserver.org)”。
开发者在实现该算法时需注意:
- 一名成员的显示名有可能因其他成员状态变化而变化。例如,若 `@user1:matrix.org` 在房间中显示为 `Alice`,当 `@user2:example.com` 也以 `Alice` 加入该房间时,两名用户都必须使用唯一化后的显示名。相反,若其中一名用户更改显示名致不再冲突,两者又可拥有自己原先的显示名。客户端需注意并确保对受影响成员正确重命名。
- 房间显示名也可能因成员名单变化而受影响。因为房间名有时基于用户显示名派生(见[计算房间显示名](#calculating-the-display-name-for-a-room))。
- 若全量遍历成员列表以查重显示名,则会导致 O(N^2) 复杂度,该实现对房间成员众多时很低效。建议客户端维护一个从 `displayname` 到使用该名成员列表的哈希表,以高效判断是否需唯一化。
##### 随消息同步展示成员信息
客户端可能希望显示发送消息成员的显示名与头像 URL。可通过检查该用户 ID 的 `m.room.member` 状态事件获取(参见[计算用户显示名](#calculating-the-display-name-for-a-user))。
在用户分页浏览历史记录时,客户端可能希望展示成员的**历史**显示名与头像 URL。由于分页时会返回旧的 `m.room.member` 事件,因此可以实现该功能。一般做法是同时维护两组房间状态:旧状态和当前状态。随着新事件到达和/或用户回溯浏览,这两组状态会逐渐分化:新事件更新当前状态,分页事件更新旧状态。当分页事件顺序处理时,旧状态即为*消息发送时*的房间状态。历史显示名和头像 URL 可由此设置。
##### 计算房间显示名
客户端可能希望显示房间的可读型名称。命名方式有多种选择。为保持不同客户端之间房间命名一致,推荐按照如下算法选择房间名:
1. 若房间具有 [m.room.name](#mroomname) 状态事件且其 `name` 字段非空,则采用该字段给出的名称。
2. 若房间有 [m.room.canonical_alias](#mroomcanonical_alias) 状态事件且该 `alias` 字段有效,则使用之。请注意,客户端在计算房间名时应避免使用 `alt_aliases`。
3. 如果以上条件都不满足,应根据房间成员组合房间名。客户端应考虑除当前用户外的 [m.room.member](#mroommember) 事件(定义如下)。
1. 若房间 `m.heroes` 数量大于等于 `m.joined_member_count + m.invited_member_count - 1`,则可利用英雄成员的事件计算用户显示名([必要时唯一化](#calculating-the-display-name-for-a-user))并拼接。比如,客户端可选择展示“Alice, Bob,以及 Charlie (@charlie:example.org)”作为房间名。客户端可根据用户体验选择限制用于生成房间名的成员数量。
2. 若英雄成员数少于 `m.joined_member_count + m.invited_member_count - 1`,且总成员数大于 1,则应用英雄成员计算显示名([必要时唯一化](#calculating-the-display-name-for-a-user)),拼接后加上剩余成员人数。例如,“Alice、Bob 及其他 1234 位成员”。
3. 若成员总数(加入和被邀请之和)小于等于 1(表明该成员为唯一成员),则依据上述规则显示房间为空。例如,“空房间(曾为 Alice)”、“空房间(曾为 Alice 及 1234 位成员)”或无成员时显示“空房间”。
客户端用 `m.heroes` 计算房间名时应对各国语言进行国际化处理。生成房间名时,客户端应尽量使用不少于 5 名英雄成员,但可根据实际需求调整数量以配合用户体验。
##### 剧透消息
{{% added-in v="1.1" %}}
消息中的部分内容可通过剧透形式在视觉上对用户隐藏。这不影响服务器对事件内容的存储,仅是在视觉上提示用户相关内容可能会暴露重要信息,导致“剧透”。
发送剧透消息时,客户端必须使用 `formatted_body`,即上文描述的 `org.matrix.custom.html` 格式。因此,支持剧透的任意 `msgtype` 都须支持该格式。
剧透内容包裹在 `span` 标签中,原因(可选)放在 `data-mx-spoiler` 属性里。若无原因,属性值可留空或未定义,但该属性不能省略。
一个剧透消息示例:
```json
{
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"body": "Alice [剧透](mxc://example.org/abc123) 在电影里。",
"formatted_body": "Alice 最终幸福地生活下去 在电影里。"
}
```
若提供原因,则如下:
```json
{
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"body": "Alice [健康剧透](mxc://example.org/abc123) 在电影里。",
"formatted_body": "Alice 最终幸福地生活下去 在电影里。"
}
```
发送剧透时,客户端应如上示例在 `body` 字段提供包含原因的备用内容。备用 `body` 字段不应包含剧透正文,因为 `body` 可能被文本类客户端或通知直接显示。为防止剧透内容被泄露,强烈推荐客户端首先将剧透正文上传至媒体仓库,然后以 markdown 链接形式引用对应 `mxc://` URI,如上述示例。
客户端应区别渲染剧透内容,并以某种显式交互提示。例如,可将剧透文本模糊化,提示用户点击后显示。
##### 媒体标题
{{% added-in v="1.10" %}}
媒体消息(包括 `m.image`、`m.file`、`m.audio`、`m.video`)可包含题注,以补充说明媒体内容。
发送标题时,客户端必须同时使用 `filename` 和 `body` 字段,`formatted_body` 及 `org.matrix.custom.html` 格式为可选。
如存在 `filename` 字段,且其与 `body` 不同,则将 `body` 视为题注,否则 `body` 视为文件名。`format` 和 `formatted_body` 仅用于题注。
{{% boxes/note %}}
在旧规范中,`body` 字段通常用于上传文件名,而 `filename` 字段仅出现在 `m.file` 上且用法一致。
{{% /boxes/note %}}
媒体消息附带题注示例:
```json
{
"msgtype": "m.image",
"url": "mxc://example.org/abc123",
"filename": "dog.jpg",
"body": "这是一张~~猫咪~~照片 :3",
"format": "org.matrix.custom.html",
"formatted_body": "这是一张 猫咪 照片 :3",
"info": {
"w": 479,
"h": 640,
"mimetype": "image/jpeg",
"size": 27253
},
"m.mentions": {}
}
```
客户端必须与媒体一起渲染标题,并应优先渲染其格式化形式。
##### 数学消息
{{% added-in v="1.11" %}}
用户可能希望在消息中发送数学符号或公式。
发送数学公式时,客户端必须使用 `formatted_body`,即采用上述 `org.matrix.custom.html` 格式。任何可用该格式的 `msgtype` 均可支持数学形式。
数学内容根据是否需要行内显示,使用 `span` 或 `div` 标签。用 `data-mx-maths` 属性书写 [LaTeX](https://www.latex-project.org/) 格式的公式。
标签内容为不能渲染 LaTeX 的客户端备用显示。可用图片、HTML 近似表示或原始 LaTeX 源文本作为备用。若用图片作为备用,发送方应注意接收端可能背景色不同所带来的显示问题。`body` 字段应包含文本表示的公式。
数学消息示例:
```json
{
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"body": "这是一个方程:sin(x)=a/b。",
"formatted_body": "这是一个方程:sin(x)=a/b"
}
```
LaTeX 语法定义不完整且有多种扩展,若客户端遇到无法渲染的语法,应优先显示备用内容。但客户端最低应支持[LaTeX2e](https://www.latex-project.org/) 的数学命令及 [TeX](https://tug.org/) 数学命令(部分命令因安全风险可例外)。
{{% boxes/warning %}}
总的说来,LaTeX 给客户端带来了安全处理压力。部分命令(如[可创建宏的命令](https://katex.org/docs/supported#macros))具潜在风险。客户端应拒绝处理此类命令,或确保安全处理(如限制递归)。客户端应以白名单方式只允许已知安全命令,而非黑名单拒绝已知不安全命令。
因此,客户端在未安全隔离环境下,不应直接调用 LaTeX 编译器渲染数学表达式,因为相关可执行文件并未设计处理不可信输入。有些 LaTeX 渲染库适合,仅允许部分 LaTeX 并限制递归深度。
{{% /boxes/warning %}}
#### 服务器行为
HomeServer 在收到不包含 `msgtype` 键,或无文本型 `body` 键的 `m.room.message` 事件时,应拒绝请求并返回 400 HTTP 状态码。
#### 安全注意事项
使用本模块发送的消息不会加密,端到端加密(E2E)仍在开发中(详见 [E2E 模块](#end-to-end-encryption))。
客户端应对**所有显示的键**进行不安全 HTML 的过滤,以防止跨站脚本(XSS)攻击。这包括房间名称和话题。