# 分享链接行内预览：查看者视角与跨端接口交接

日期：2026-09-28。产品规则已由用户确认。本文是需求与接口建议，不代表后端或手机端已经实现。

## 1. 核心需求

即我内部分享链接在消息中直接显示蓝色文字预览，不增加下方卡片。单条消息显示“原作者：摘要”；**只有当前查看者就是原作者时，才显示“我：摘要”**。

“我”相对于看消息的人，不相对于复制链接的人、发送链接的人、群主或 AI 会话所有者。规则统一覆盖发给自己、私聊和群聊，后续 Flutter、插件、Web 分享页使用相同语义。

例：A 的原消息是“明天下午讨论方案”。A 分享给 B，B 再转发给 C：

| 查看场景 | 行内蓝字 |
| --- | --- |
| A 发给自己，A 看 | 我：明天下午讨论方案 |
| A 发到私聊或群聊，A 看 | 我：明天下午讨论方案 |
| B 收到 A 的分享 | A 的昵称：明天下午讨论方案 |
| C 收到 B 的转发 | A 的昵称：明天下午讨论方案 |
| B 把该链接发回 A，A 看 | 我：明天下午讨论方案 |
| A 分享 B 的原消息，A 看 | B 的昵称：原消息摘要 |
| 原作者 B 看同一链接 | 我：原消息摘要 |

外层头像仍表示“本条链接是谁发的”；蓝字只表示原内容，不能用外层消息的 `isMe` 来判断原作者。正文、链接目标不改写，不把带“我”的预览文字保存为分享快照或跨端消息正文。

## 2. 最优先缺少什么接口能力

**不必等统一批量预览接口。先给现有单条分享解析补齐可信的原作者身份判断，即可解决主要缺口。**

现有入口：

- 已登录解析：`POST /api/v1/chats/messages/copy-link/resolve`，请求 `{ "sid": "..." }`。
- 公开快照：`POST /api/public/v1/chats/messages/copy-link/detail`，请求 `{ "sid": "..." }`。

当前插件能够做的：

- 认证解析返回 `access_mode: normal` 且有匹配的 `source_context.anchors[].record_owner_user_id` 时，可以按当前登录账号判断原作者。
- 不额外加载延展、原会话历史或原始媒体；现有解析快照用于生成短摘要。
- 个人快记实测也可能返回 `access_mode: link_read_only`，仅有 `sender_display_name`、`sender_avatar_url`、正文、时间、类型，没有原作者账号或来源锚点。
- 上述缺身份的情况目前保留原昵称。不能通过昵称相同、头像相同、文本相同或“是我转发的”来猜本人。

后端最小交付：所有支持的单条来源——个人快记、主题记录、私聊/群聊消息、角色消息——在认证解析中返回可信的“当前查看者是否原作者”。对于 AI/Bot 发言，不能把拥有会话或 Bot 的用户当成原作者。

## 3. 兼容现有接口的字段建议（待后端确认，不是已存在字段）

保留 `items[].sender_display_name` 等现有快照字段，在每个 item 增加：

```json
{
  "sender_display_name": "原作者昵称或原角色名",
  "text_content": "原消息正文",
  "sender_identity": {
    "kind": "user",
    "viewer_is_author": true
  }
}
```

| 字段 | 建议合同 |
| --- | --- |
| `sender_identity.kind` | `user` / `self_role` / `bot` / `agent` / `unknown` |
| `sender_identity.viewer_is_author` | `true`：服务器确认当前登录者是原作者；`false`：确认不是；`null` 或缺失：无法确认或没有登录身份 |
| `sender_display_name` | 原作者或原角色的分享快照名称，不能写入相对称呼“我”“你”，不能用分享者名称替代 |

服务端从认证上下文确定查看者，不相信客户端提交的 `viewer_user_id` 或昵称。优先返回判断值，不必为了显示“我”向公开访问者额外暴露原作者账号 ID。

具体要求：

1. `link_read_only` 是访问模式，不代表原作者一定不是当前用户。即使只读分享，也需根据可信来源保留的归属信息计算身份。
2. 多次转发、跨群分享不能覆盖最初作者归属；`shared_by_user_id` 不是原作者身份。
3. `self_role` 的本人判断使用角色所属账号；本人按产品要求显示“我”，其他人保留分享快照中角色名。不同角色不能拿当前用户资料强行替换。
4. Bot/AI 内容不因为当前用户创建了 Bot、拥有会话或发起任务就变成“我”；用户在 AI 会话中自己写的提问应按实际发言身份区分。
5. 公开未登录访问不得使用分享创建者或上一个访问者的登录身份；未知时返回 `null`/不返回，保留原作者名称。
6. 分享撤销、审核、无权限等状态不能连带返回原本不可见的正文摘要。身份字段不能扩大原内容访问权限。
7. 老链接应尽可能从原分享来源索引补齐身份；没有可信来源的老数据保留未知，不批量猜测或篡改。

## 4. 各端统一呈现规则

```text
原作者是当前查看者： 🔗 我：摘要……
原作者是其他人：     🔗 原作者昵称：摘要……
身份不可确认：       🔗 原快照昵称：摘要……
多选记录：           🔗 记录标题 · N 条记录
长文：               🔗 长文标题
```

- 单行蓝字、小链接图标、超长省略；不额外加头像、时间、来源群名或下方卡片。
- 多选记录整体不加“我：”；使用分享标题和真实条数。不要全局替换标题或正文里的“我”，例如“我的产品想法”是原文，不是作者前缀。
- 链接旁的说明仍为普通文字，不因获取预览变成可点击蓝字。
- 点击沿用已有分享打开流程；不预先加入群、发起通话、安装扩展或执行其他动作。
- 不附加未被授权分享的群名、私聊对象、上下文、回复和整天录音；不是所有能在原会话里读取的内容都能放进链接预览。
- 无有效预览时保留类别标签或明确的失效/权限提示，链接目标保留，不用猜测内容。
- 手机端和 Web 需要分别实现这个显示逻辑；只更新插件不会自动改变其他客户端。

## 5. 缓存与传输边界

`viewer_is_author` 是当前查看者相关的结果，不是链接的全局固定属性。

- 带此字段的完整结果必须按环境、账号、链接及必要的权限版本隔离；禁止仅按 URL 全局/CDN 缓存后复用于其他用户。
- 可共享的公开快照与查看者身份判断应分开缓存。公开快照中不可永久写入任何用户的“我”。
- 退出/切换账号，立即清除旧“我”及旧内容，未完成的旧请求不得覆盖新用户结果；下一次解析前显示通用标签。
- 当前插件已经采用账号/环境隔离短缓存、可见链接加载、请求去重和取消；服务端仍须实现自己的隔离和授权。
- 正常发送、复制原链接、转发仍保留 URL 和原正文，预览失败不阻止发送。未来新接口不要求历史消息重发。

## 6. 后续轻量预览接口（第二优先级）

可在最小身份字段落地后，再新增统一无副作用、批量、短摘要接口。名称尚未约定，`POST /api/v1/shares/preview/batch` 仅为建议。

最小输出建议：每项链接标识、类型、标题、原作者快照、查看者身份判断、短摘要、真实条数及可用状态。必须明确请求数量与响应大小上限、身份来源、失效及权限变更的处理。

预览不得计为消息已读/通话查看，不写最近访问，不加入、绑定、呼叫、安装或重新审核。已结束通话目前详情会记查看，需要专用无副作用预览能力；不能直接用于自动预览。

其他内部链接类型盘点见 [行内预览方案](internal-share-link-preview-proposal-2026-09-28.md)。

## 7. 联调验收

- 用 A、B、C 三个账号逐项验证第 1 节场景，覆盖发给自己、私聊、群聊和转发回来。
- A 与 B 昵称/头像相同时仍正确区分，不从外层消息左右方向判断。
- 同 URL 从 A 切到 B，旧“我”立即消失；注销、重登、网络迟到结果不会串账号。
- 个人快记 `link_read_only`：本人是“我”，其他人是原昵称；旧数据没有可靠身份时保留原昵称。
- 角色消息、Bot/AI 回复、AI 会话中的用户提问分别正确归属。
- 多选标题和长文标题保持原文；摘要不会改写正文中的第一人称。
- 未登录分享页显示原昵称或通用标签，不出现从其他查看者继承来的“我”。
- 撤销/审核/无权限分享不泄露内容，不自动读取来源私聊、群名及未分享的上下文。
- 老消息无须重发；原 URL 和原正文不被预览文案覆盖。

## 8. 当前交付状态

插件现有能力已经统一按查看者判断，三个会话场景共用富文本组件。已补自动化回归，包括转发回来和切换账号；测试以接口替身验证跨账号语义，不代表已用三个真实账号完成跨端联调。

未修改手机端或后端，未调用真实发送/转发。后端字段落地后需按最终合同接入，再完成 A/B/C 多账号及 Flutter/Web 联调。
