6.6 KiB
线程
{{% added-in v="1.4" %}}
线程允许用户在一个房间中以可视方式分支他们的对话。通常在线上讨论多个主题时使用,线程相较于传统的 富回复 能够提供更有组织的交流方式,而富回复未必能够兼顾所有场景。
客户端应当在时间线上以区别于普通消息或回复的方式渲染线程,例如为线程提供一些上下文信息,但将完整的对话历史隐藏于可展开内容之后。
线程通过 rel_type
设为 m.thread
来建立,并引用 线程根(即该线程事件所指向的主时间线事件)。无法从本身已是事件关系子事件(即带有 m.relates_to
和 rel_type
属性的事件,参考 关系类型)创建线程。因此,线程也无法嵌套。
与富回复链不同,线程中的所有事件都引用线程根,而不是最新的消息。
下面通过示例展示线程及其形成方式:
{
// 已省略无关字段
"type": "m.room.message",
"event_id": "$alice_hello",
"sender": "@alice:example.org",
"content": {
"msgtype": "m.text",
"body": "Hello world! How are you?"
}
}
{
// 已省略无关字段
"type": "m.room.message",
"event_id": "$bob_hello",
"sender": "@bob:example.org",
"content": {
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$alice_hello"
},
"msgtype": "m.text",
"body": "I'm doing okay, thank you! How about yourself?"
}
}
{
// 已省略无关字段
"type": "m.room.message",
"event_id": "$alice_reply",
"sender": "@alice:example.org",
"content": {
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$alice_hello" // 注意:始终指向 *线程根*
},
"msgtype": "m.text",
"body": "I'm doing great! Thanks for asking."
}
}
如上所示,任何没有 rel_type
的事件都可以仅通过 m.thread
关系被引用来成为线程根。
非线程化客户端的回退机制
能够理解线程的客户端应直接以线程方式处理,但某些客户端(出于历史原因或功能范围限制)可能无法向用户有效展现对话历史。
为此,支持线程的客户端发送事件时应包含 富回复 元数据,以尝试形成对话的回复链。这种方式在高线程活跃房间中并不理想,但可以为用户提供与房间内其他消息相关的上下文信息。
该兼容方式通过合并两种关系并为 is_falling_back
标记设为 true
实现。
// 在事件内容中……
"m.relates_to": {
// m.thread 关系结构
"rel_type": "m.thread",
"event_id": "$root",
// 富回复结构
"m.in_reply_to": {
// 线程中客户端已知的最新消息,应选取其他客户端有较大渲染概率的事件,
// 如 `m.room.message` 事件。
"event_id": "$target"
},
// 标记此事件为带回复回退的线程
"is_falling_back": true
}
{{% boxes/note %}}
对于对线程有一定感知(即不直接渲染线程、但知道规范中有该功能)的客户端,可以将对带有 rel_type
为 m.thread
事件的富回复视作线程内部的回复,以实现线程客户端侧的对话连续性。
实现方法为:从被回复事件中复制出 event_id
(线程根),添加 m.in_reply_to
元数据,并在 m.relates_to
中加入 is_falling_back: true
。
{{% /boxes/note %}}
线程内的回复
在 非线程化客户端的回退机制 部分,为 m.relates_to
新增了 is_falling_back
字段。当未提供该字段时,默认为 false
,这同样允许线程消息本身作为回复。
除了 is_falling_back
为 false
(或未指定)以外,客户端应利用非线程化客户端的回退机制在线程内创建回复,并据此渲染事件。
服务器行为
m.thread
关系的验证
服务器应拒绝客户端针对带有 m.relates_to
属性的事件尝试发起线程的请求。如果客户端试图对带有 m.relates_to
属性的事件作为目标事件,则应返回 HTTP 400 错误及相应错误信息,按照 标准错误响应 结构处理。
{{% boxes/note %}}
此种情况目前没有单独的错误码:服务器应与 HTTP 400 一同返回 M_UNKNOWN
。
{{% /boxes/note %}}
服务器侧对 m.thread
关系的聚合
由于线程总是引用线程根,一个事件将拥有多个“子事件”,共同组成该线程。服务器应对这些事件进行 聚合。
线程聚合的数据包括用户在该线程中的参与情况、线程(服务器已知范围内)大致的事件数量,以及线程内最新(按服务器视角的拓扑顺序)的一条消息。
与任何其他子事件聚合一样,m.thread
聚合结果通过 unsigned
下的 m.relations
属性返回给线程根。例如:
{
"event_id": "$root_event",
// 未显示无关字段
"unsigned": {
"m.relations": {
"m.thread": {
"latest_event": {
// 线程中最新事件的序列化副本。
// 部分字段为简化未示出。
"event_id": "$message",
"sender": "@alice:example.org",
"room_id": "!room:example.org",
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "Woo! Threads!"
},
"unsigned": {
"m.relations": {
// ...
}
}
},
"count": 7,
"current_user_participated": true
}
}
}
}
latest_event
为线程中由未被忽略的用户发送,服务器视拓扑顺序最新的一条事件。
注意,正如上例,latest_event
的子事件本身也需被聚合并包含在该事件下的 m.relations
下。服务器需注意避免形成循环,尽管由于 m.thread
不允许指向带有 m.relates_to
属性的事件,目前不可能产生循环。
count
仅指向目标事件的 rel_type
为 m.thread
的事件数量,未包含被忽略用户发送的事件。
current_user_participated
为 true
时,表明认证用户满足以下任一条件:
- 是线程根事件的
sender
; - 是某个引用线程根且
rel_type
为m.thread
的事件的sender
。
查询房间内线程
客户端如需获取某线程内的所有事件,可通过
GET /relations/{threadRootId}/m.thread
;
如需获取某房间内所有线程,则需专用 API:
{{% http-api spec="client-server" api="threads_list" %}}