# 可选:完整回答去哪(三档,默认零上传) > 三档的取舍、设备码绑定、短链接的实现细节。网站入口见 [README](../README.md)。 > **全文里唯一涉及「登录」的地方就是这一节,而且只有第 3 档需要。** > 默认档 `chat` 不需要任何账号,也不连任何服务器。 **要解决的问题**:通知里那句摘要是给人**扫一眼**的,遇到长回答必然被截掉 —— "到底改了哪几个文件、结论是什么"这类细节只有原文里才有。 但"正文放哪儿"这件事,不同的人答案不一样:有人根本不想让任何东西离开自己的机器, 有人愿意换一个能点开的链接。所以这是一道**你自己选的题**: | 模式 | 正文去哪 | 服务器上有东西吗 | 推送里有什么 | |---|---|---|---| | `chat`(**默认**) | 切成几条**直接发到 QQ 聊天框** | **没有**(一个字节都不传) | 摘要一条,正文随后分条发出 | | `note` | 存到服务器(`/var/www/dsh-notes/`) | 有,按保留期自动删 | 只有摘要,**不给链接** | | `note-link` | 同上,并给一个可点开的短链接 | 有 | 摘要 + `[查看完整回答](…)` | **怎么切换**:**都在设置里** —— 设置 →「QQ 远程提醒与跨会话记忆」→ 分组「QQ 远程提醒」: 模式是 `qqFulltextMode`(「完整回答怎么给」),总闸是 `cloudEnabled`(「允许上传正文」)。 > ⚠️ **这两个开关不在输入框下面,这是刻意的:** > 输入框下方**只留**「QQ 提醒」「协作」——那是抬手就要按的(开了没开必须一眼看到); > 全文去向属于"配置",一年也改不了几次,摆在输入框旁边只是噪音。 > **功能一个都没少**,只是搬回了设置面板(同一张 `FIELD_SPECS` 字段表、同一套白名单校验)。 > `scripts/verify-ui-installed.mjs` 有两条断言分别守着「输入框下面恰好 2 个开关」 > 和「设置里必须有 `qqFulltextMode` / `cloudEnabled` 这两个字段」。 **三道闸,缺一不可**(这是刻意的设计,不是绕): 1. 模式必须是 `note` / `note-link`; 2. `cloudEnabled` 总闸必须**显式打开**(默认关); 3. `notesEnabled` 得是开的,且**至少有一个地方能存**:云端(`cloudUrl` + `cloudToken`)或中枢(`hubUrl`)。 任一条不满足 ⇒ **按「直接发到 QQ」处理,不传正文**,并在日志里写明是哪一条挡住了 (`完整回答模式是「…」,但云上传总闸关着(cloudEnabled=false),这次按「直接发到 QQ」处理`)。 所以「默认关闭上传」不是一句承诺,而是**可以被观察到的行为**: `chat` 档 + 总闸关闭时,插件对本机以外的服务器发送的字节数是 0(探针实测)。 **分条的长度**:`QQ_TEXT_SAFE_CHARS = 900`(`src/fulltext.js`),按段落 → 行 → 硬切三级切分, **保证一个字符都不丢**,并给每段加 `(1/7)` 这样的序号(序号算在 900 之内,不会让某条溢出)。 超长回答先被 `qqFulltextMaxChars`(默认 6000 字)截断,再分条。 > **900 这个数字是实测过的**:官方文档只写了错误码「消息长度超限 / 消息过长或异常」, > **没给字数**。2026-10-03 用真实 QQ 接口实测:**纯文本 900 / 1500 / 3000 字服务端全部接受** > (三条真发出去、拿到消息 id)。也就是说 900 的安全边际很足 —— 方向选择上**宁可多发两条, > 也不冒"整条发不出去、正文白丢"的风险**。想少刷屏可以把这个常量调大, > 但那属于"平台哪天改规则就失效"的自负,故留保守值。 ## 可选·第 3 档需要登录:云端账号(设备码绑定)与「记录挂在自己账号下」 上面那张表里的「服务器」有两个不同的地方,**别混**: | | 中枢(`hubUrl`) | 云端网页端(`cloudUrl`,0.8.8 起) | |---|---|---| | 记录归谁 | 谁都不知道,**公开可读**的 `.md` | **你自己的账号**,登录才能看 | | 鉴权 | 一把共享令牌 | **每账号一把**令牌(设备码绑定换来的) | | 配额 | 无 | 免费版 100 次/天、记录留 5 小时;付费版 1000 次/天、留 48 小时 | | 链接 | `…/dsh/.md`(不带登录) | `…/n/`(要登录,且**每打开一次算 1 次**) | **配了云端就走云端**(`cloudUrl` + `cloudToken` 都有值时),否则走中枢。 ⚠️ 云端失败时**不会**偷偷改发中枢 —— 那等于把内容转发去另一个可见性不同的地方; 失败就只记日志、推送退回"只有摘要"。 **怎么绑定(不用手抄令牌)**:让 DSH 调一次工具 `cloud_bind`: 1. 它调用 `/api/device/start` 拿到一串 **8 位短码**(如 `ABCD-EFGH`),念给你; 2. 你打开 `cloudUrl`、登录,在「账号」页填入这串码确认; 3. 再调一次 `cloud_bind`,它换回令牌并**自动保存**到配置里。 短码单独泄露**换不出任何权限**:服务端规定"必须已登录才能确认",且令牌**只交付一次**; 令牌在服务端只存 `sha256`,明文从不落盘。 另外两个工具:`cloud_status`(看绑到哪个账号、什么档位、今天还剩几次)、 `cloud_unbind`(删掉本机令牌;⚠️ 服务端那把仍需到网页端「我的令牌」里吊销)。 **安全边界(写清楚,别指望别人猜)**:这把发布令牌能**发布**、能读**配额与档位**, 但**读不到任何记录正文** —— `/api/records*` 只认登录 cookie。 > **云端地址填什么**(自建或换实例时看这里): > > - **填 `https`**(如 `https://cyanovo.top`)—— API 走 TLS,令牌不过明文网络; > - QQ 里的链接**仍然**会被改写成 `http://大写域名/n/`(唯一实测能点开的形式), > 所以要求「**这个域名的 80 端口也在服务**」。作者那个实例的 80/443 现在同时开着, > 两种写法都能用。 > - **别填带端口的**(如 `http://x:8444`):端口会被 QQ 的链接改写丢掉,链接必然打不开。 > > 插件判断不了你服务器的 80 通不通,所以会在**设置 → 运行状态**里留一条提示让人确认。 ## 可选·第 3 档细说:存服务器 + 短链接(`note-link`) 会话转空闲时,插件把本轮的**用户提问原文 + 助手回答原文**(未删节) POST 到中枢,中枢把它落成 `/var/www/dsh-notes/.md`,推送文案里追加一行链接: ``` ✅ main 跑完了 把协作模式写进插件了 [查看完整回答](https://notes.example.com/dsh/k3f9a.md) ``` - QQ 走 **markdown 消息**(`msg_type=2`)时,上面那行渲染成一句**可点的文字**, 而不是把长 URL 铺满屏幕(`qqMarkdown: true`,默认开)。 - 万一 markdown 不被接受(权限、长度、内容被拒),插件会**自动退回纯文本重发** —— 链接完整显示出来,通知不会丢。 **为什么链接是静态返回、不经过中枢**:nginx 用**正则 location** 把 `/dsh/.md` 映射到静态目录,所以**中枢挂了,已经发出去的链接照样打得开**。正则同时限定了 id 的形状, `../` 之类根本匹配不上,从匹配层面杜绝目录穿越(而不是靠事后过滤)。 **id 只有 5 位**(`notesIdLength`,36^5 ≈ 6047 万种)。**这是一条要你自己权衡的取舍**: 链接**不需要登录**就能打开,短 id 好记、也就更好猜。所以: - 别把带密钥、带隐私的会话拿来做测试; - 想更安全就把 `notesIdLength` 调大(比如 12):链接长一点,猜中概率低得多; - 彻底不想公开就 `notesEnabled: false`,退回"只有摘要"的老行为。 **页面长什么样**:`.md` 以 `text/markdown; charset=utf-8` 返回,浏览器直接显示原文 (标题 + 工作区 + 时间 +「我这一轮说了什么」+「完整回答」)。 超过 `notesMaxChars` 会**明确写出被截断了多少**,不会让人误以为那就是全部。 **失败怎么办**:上传超时(默认 6 秒)、中枢 500、DNS 挂了 —— 全部只记一条日志, **推送照发**,只是这次没链接。宁可少一个链接,也不能因为服务器抽风就丢掉「跑完了」这条通知。 --- [← 回到 README](../README.md)