dsh-qq-moony 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.7.0 — 2026-10-25
4
+
5
+ 把"我自己的东西"从包里拿掉——别人装的是插件,不是我。
6
+
7
+ - **修:包里的 patch 不再写死我的 token / 小号号 / 人设。** 之前 `cordis.patch.yml` 里带着作者自己的 `token` 和 `uin`,别人装上会直接拿它去连——连不上还算好的,连上了更糟。现在一律留空,由各人在 `config.json` 里填。
8
+ - **新增:`persona`(人设)配置项,默认空。** 留空时,自动回复的会话只被告知"你替这个账号主人在 DSH 里收发消息"——中性的,不会变成别人的角色。填了才用你写的人设。设置页里也加了一块文本框。
9
+ - **措辞中性化**:提示词里原来写死的"你是溟月""主人"改成"账号主人",不再把我的身份塞给别人。
10
+
11
+ **升级提醒**:已经装过的人重新装一次即可;如果之前在 `config.json` 里填过端口和 token,这次会真正生效(0.6.0 修了优先级)。
12
+ ## 0.6.0 — 2026-10-25
13
+
14
+ 有人报告"装上了但一直连不上、还反复自己重启",根因是两个 bug 叠在一起:
15
+
16
+ - **修:配置优先级反了。** README 写的是"本地 config.json 优先",代码里却是包的 patch 排在最后、压掉了本地文件——用户改的端口和 token 从来没生效过。现在顺序是 `默认 < patch < 本地文件`。
17
+ - **修:端口被别的服务占着时白折腾。** 原来只看"请求通不通",有人机器上 3000 早被别的网页服务占了(回的是 `{"code":90004,"message":"资源不存在"}`),插件误判成 NapCat 挂了,于是反复拉起、每次白等一分钟。现在探活区分三种:`ok` / `foreign`(端口被占)/ `down`,被占时不自动拉起,设置页直接说"3000 被别的服务占了,换个端口或者把它挪开"。
18
+
19
+ **给改装的人**:如果 3000 上有别的东西,改 `baseUrl`(比如 `http://127.0.0.1:3010`),同时把 NapCat 的 OneBot HTTP 端口也改成一�样。
3
20
  ## 0.5.2 — 2026-10-25
4
21
 
5
22
  - 拉起 NapCat 时不再弹黑窗口:改成生成 VBS、用 `wscript` 静默执行(`windowsHide` 对批处理无效)
package/README.md CHANGED
@@ -82,6 +82,19 @@ NapCat 是接管整个 QQ 客户端的行为——消息收发、登录态、空
82
82
 
83
83
  ---
84
84
 
85
+ ### 人设:默认是中性,不会变成作者的角色
86
+
87
+ 包里**不带任何人的身份**。`persona` 留空时,自动回复的会话只被告知"你替这个账号主人在 DSH 里收发消息"——干净、中性,你说什么它就是什么。
88
+
89
+ 想让它有自己的性格,就在设置页「人设」那一栏写几句,或者改 `config.json` 的 `persona`:
90
+
91
+ ```json
92
+ "persona": "你是这个账号主人在 DSH 里养的助手,说话短、口语,不自我介绍"
93
+ ```
94
+
95
+ 填了之后,这段话会跟着每条自动回复一起投给会话。
96
+
97
+ ⚠️ 同理,**包里也不带作者的 token 和小号号**。`cordis.patch.yml` 里这几项都是空的,必须你自己在 `config.json` 填。
85
98
  ## 四、配置
86
99
 
87
100
  优先级:**本地配置文件 > bundle patch 里的 config > 代码默认值**。
package/client.js CHANGED
@@ -109,6 +109,7 @@ window.__ModuleLoader__.load({
109
109
  qzoneMinMin: 状态?.qzoneMinMin ?? 120,
110
110
  qzoneMinMax: 状态?.qzoneMinMax ?? 240,
111
111
  qzonePrompt: 状态?.qzonePrompt ?? '',
112
+ persona: 状态?.persona ?? '',
112
113
  groupWhitelist: (状态?.groupWhitelist ?? []).join(' '),
113
114
  groupContextSize: 状态?.groupContextSize ?? 30,
114
115
  groupEveryMinMin: 状态?.groupEveryMinMin ?? 8,
@@ -139,6 +140,7 @@ window.__ModuleLoader__.load({
139
140
  qzoneMinMin: Number(值.qzoneMinMin),
140
141
  qzoneMinMax: Number(值.qzoneMinMax),
141
142
  qzonePrompt: String(值.qzonePrompt ?? ''),
143
+ persona: String(值.persona ?? ''),
142
144
  groupWhitelist: String(值.groupWhitelist ?? ''),
143
145
  groupContextSize: Number(值.groupContextSize),
144
146
  groupEveryMinMin: Number(值.groupEveryMinMin),
@@ -159,7 +161,9 @@ window.__ModuleLoader__.load({
159
161
  const 缺口 = [];
160
162
  if (!状态.qqnt) 缺口.push('没找到 QQ NT —— 去 im.qq.com 装新版(9.9.x)。老版 9.7 注不进去,这是死条件。');
161
163
  if (!状态.napcat) 缺口.push('没找到 NapCat —— 去 github.com/NapNeko/NapCatQQ 下 Shell 版,解压后把 napCatDir 指过去。');
162
- if (状态.qqnt && 状态.napcat && !状态.online)
164
+ if (状态.portState === 'foreign')
165
+ 缺口.push('端口上坐着的不是 NapCat —— 3000 被别的服务占了。换个端口(改 baseUrl),或者把那个服务挪开。');
166
+ else if (状态.qqnt && 状态.napcat && !状态.online)
163
167
  缺口.push('QQ NT 和 NapCat 都在,但连不上 —— 检查 NapCat 启没启、3000 端口、token 三样。');
164
168
 
165
169
  return h(
@@ -201,6 +205,39 @@ window.__ModuleLoader__.load({
201
205
  ),
202
206
  ),
203
207
  ),
208
+ h(
209
+ 'div',
210
+ { style: { ...卡片, alignItems: 'flex-start' } },
211
+ h(
212
+ 'div',
213
+ { style: { flex: 1 } },
214
+ h('div', { style: 标题 }, '人设(可选)'),
215
+ h(
216
+ 'div',
217
+ { style: 说明 },
218
+ '填了就把这段当作「你是谁」交给自动回复的会话;留空则用中性的“替账号主人收发消息”。不想让这个号变成别人的角色,就别填。',
219
+ ),
220
+ h('textarea', {
221
+ rows: 3,
222
+ value: 值.persona,
223
+ placeholder: '例:你是这个账号主人在 DSH 里养的助手,说话短、口语,不自我介绍',
224
+ onChange: (e) => 数字('persona', e.target.value),
225
+ style: {
226
+ width: '100%',
227
+ marginTop: '8px',
228
+ padding: '8px 10px',
229
+ borderRadius: '10px',
230
+ border: '1px solid var(--dsw-alias-border-2, rgba(127,127,127,0.35))',
231
+ background: 'transparent',
232
+ color: 'inherit',
233
+ fontSize: '12px',
234
+ lineHeight: '19px',
235
+ resize: 'vertical',
236
+ boxSizing: 'border-box',
237
+ },
238
+ }),
239
+ ),
240
+ ),
204
241
  h(
205
242
  'div',
206
243
  { style: { ...卡片 } },
package/cordis.patch.yml CHANGED
@@ -1,12 +1,15 @@
1
1
  # dsh-qq-moony bundle patch
2
2
  # 把本包插进 profile 的层叠配置里。装好后 profile 只要在 bundles 里列出本包即生效。
3
+ #
4
+ # ⚠️ 这里只放「所有人都一样」的默认值。token / uin / 端口 / 人设这些属于各人自己的,
5
+ # 一律留空,由用户在 <DSH_HOME>/qq-moony/config.json 里填——本地文件优先级最高。
3
6
  - insert:
4
7
  - id: qq-moony
5
8
  name: dsh-qq-moony
6
9
  config:
7
10
  enabled: true
8
11
  baseUrl: 'http://127.0.0.1:3000'
9
- token: 'moony2026'
10
- uin: 3595047163
12
+ token: ''
13
+ uin: 0
11
14
  perHour: 20
12
15
  perDay: 60
package/index.js CHANGED
@@ -60,6 +60,8 @@ const 默认配置 = {
60
60
  qzonePrompt: '',
61
61
  qzoneEveryMinMin: 120,
62
62
  qzoneEveryMinMax: 240,
63
+ // 人设:留空则用中性说明;填了就把这段当作"你是谁"交给会话
64
+ persona: '',
63
65
  // DSH 启动时自动把 NapCat 拉起来(免扫码,用 -q 快速登录)
64
66
  autoStartNapCat: true,
65
67
  napCatDir: '',
@@ -114,7 +116,7 @@ const 工具输出 = (字段, 渲染) => ({
114
116
  });
115
117
 
116
118
  export function apply(ctx, 配置) {
117
- const c = { ...默认配置, ...读取本地配置(), ...(配置 ?? {}) };
119
+ const c = { ...默认配置, ...(配置 ?? {}), ...读取本地配置() };
118
120
  const 账本 = join(家目录(), 'sent.log');
119
121
  const 收件箱 = join(家目录(), 'inbox.jsonl');
120
122
 
@@ -133,22 +135,40 @@ export function apply(ctx, 配置) {
133
135
  return { qqnt, napcat };
134
136
  }
135
137
 
136
- /** DSH 起来时顺手看一眼 NapCat:没跑就自己拉起来(免扫码,用 -q 快速登录)。 */
137
- async function 确认NapCat在跑() {
138
- if (c.autoStartNapCat === false) return;
139
- const 探活 = async () => {
138
+ /** 探活:区分「NapCat 在」「端口被别的服务占了」「根本没起来」三种情况。 */
139
+ async function 探NapCat() {
140
+ try {
141
+ const 头 = c.token ? { Authorization: `Bearer ${c.token}` } : {};
142
+ const r = await fetch(`${c.baseUrl}/get_login_info`, { headers: 头, signal: AbortSignal.timeout(4000) });
143
+ const 文本 = await r.text();
144
+ let 体 = null;
140
145
  try {
141
- const 头 = c.token ? { Authorization: `Bearer ${c.token}` } : {};
142
- const r = await fetch(`${c.baseUrl}/get_login_info`, { headers: 头, signal: AbortSignal.timeout(4000) });
143
- return r.ok;
146
+ 体 = JSON.parse(文本);
144
147
  } catch {
145
- return false;
148
+ /* 不是 JSON,多半是别的服务 */
146
149
  }
147
- };
148
- if (await 探活()) {
150
+ if (r.ok && 体?.status === 'ok') return 'ok';
151
+ // 有东西在应答,但不是 OneBot——端口被别的程序占了
152
+ return 体 ? 'foreign' : 'foreign';
153
+ } catch {
154
+ return 'down';
155
+ }
156
+ }
157
+
158
+ /** DSH 起来时顺手看一眼 NapCat:没跑就自己拉起来(免扫码,用 -q 快速登录)。 */
159
+ async function 确认NapCat在跑() {
160
+ if (c.autoStartNapCat === false) return;
161
+
162
+ const 状态 = await 探NapCat();
163
+ if (状态 === 'ok') {
149
164
  记调试('NapCat 已经在跑,不用拉');
150
165
  return;
151
166
  }
167
+ if (状态 === 'foreign') {
168
+ // 端口上坐着别的程序:这时候去拉起 NapCat 只会白折腾
169
+ 记调试(`${c.baseUrl} 上应答的不是 NapCat(多半被别的服务占了),不自动拉起`);
170
+ return;
171
+ }
152
172
  const 目录 = c.napCatDir || '';
153
173
  const 号码 = String(c.qqUin || c.uin || '');
154
174
  if (!目录 || !号码) {
@@ -179,7 +199,7 @@ export function apply(ctx, 配置) {
179
199
  // 给它一点时间登录,最多等一分钟
180
200
  for (let i = 0; i < 30; i += 1) {
181
201
  await new Promise((r) => setTimeout(r, 2000));
182
- if (await 探活()) {
202
+ if ((await 探NapCat()) === 'ok') {
183
203
  记调试(`NapCat 起来了(等了约 ${(i + 1) * 2} 秒)`);
184
204
  return;
185
205
  }
@@ -262,6 +282,15 @@ export function apply(ctx, 配置) {
262
282
 
263
283
  const 群下次机会 = new Map();
264
284
 
285
+ /** 投递时给会话的"你是谁"说明:默认中性;填了 persona 才用主人自己写的人设。 */
286
+ function 身份说明() {
287
+ const 人设 = String(c.persona ?? '').trim();
288
+ if (!人设) {
289
+ return '这是你的 QQ 通道,你替这个账号的主人在 DSH 里收发消息。用账号主人平时的语气说话,别自我介绍。';
290
+ }
291
+ return `${人设}\n(同一个工作区、同一份记忆,memory/ 下的文件随时可看。)`;
292
+ }
293
+
265
294
  /** 把来信投给 QQ 专用会话,让那边的 agent 去回。失败只记日志,不影响收消息。 */
266
295
  function 自动回复(记录) {
267
296
  记调试(
@@ -301,7 +330,7 @@ export function apply(ctx, 配置) {
301
330
  const 回哪里 = 是群 ? `group:${记录.group}` : String(记录.from);
302
331
  const 提示 =
303
332
  `【QQ ${是群 ? `群 ${记录.group} 里被点名` : '来信'}】${记录.name}(${记录.from})在 ${记录.t} 说:${描述}\n\n` +
304
- `你是溟月,这是你的 QQ 自动回复通道——同一个工作区、同一份记忆,memory/ 下的文件随时可看。\n` +
333
+ `${身份说明()}\n` +
305
334
  `用 qq_send 回给 ${回哪里}${是群 ? '(群里只回一句,别接着聊)' : ',一句就够'},短,像平时说话那样,别解释你在做什么。回完就停。`;
306
335
  const 等待 = c.replyDelayMinMs + Math.random() * Math.max(0, c.replyDelayMaxMs - c.replyDelayMinMs);
307
336
  const 记忆 = 读记忆片段();
@@ -640,8 +669,8 @@ export function apply(ctx, 配置) {
640
669
  记调试(`空间模式:给它一次发说说的机会(下一次排在 ${Math.round(下次.分钟)} 分钟后)`);
641
670
  投递到会话(
642
671
  `【QQ 空间】到了看看要不要发条说说的点。\n` +
643
- (要求 ? `主人给的要求:${要求}\n` : '没有特别要求:想发就发一条轻的,不想发就结束这一轮。\n') +
644
- `想发就用 qq_qzone;只发轻的,不点名、不写具体地点、不提主人。发完就停,别连着发第二条。${记忆 ? `\n\n${记忆}` : ''}`,
672
+ (要求 ? `账号主人给的要求:${要求}\n` : '没有特别要求:想发就发一条轻的,不想发就结束这一轮。\n') +
673
+ `想发就用 qq_qzone;只发轻的,不点名、不写具体地点、不提账号主人。发完就停,别连着发第二条。${记忆 ? `\n\n${记忆}` : ''}`,
645
674
  );
646
675
  }, 60000);
647
676
 
@@ -668,16 +697,20 @@ export function apply(ctx, 配置) {
668
697
  const 记录 = 读账(账本);
669
698
  let 在线 = false;
670
699
  let 昵称 = '';
671
- try {
672
- const r = await 调('get_login_info', {});
673
- 在线 = true;
674
- 昵称 = String(r.data?.nickname ?? '');
675
- } catch {
676
- /* 离线就报离线 */
700
+ 在线 = (await 探NapCat()) === 'ok';
701
+ if (在线) {
702
+ try {
703
+ const r = await 调('get_login_info', {});
704
+ 昵称 = String(r.data?.nickname ?? '');
705
+ } catch {
706
+ /* 探到了但取名字失败,别当离线 */
707
+ }
677
708
  }
709
+ const 端口状态 = 在线 ? 'ok' : await 探NapCat();
678
710
  发json(res, 200, {
679
711
  ok: true,
680
712
  online: 在线,
713
+ portState: 端口状态,
681
714
  nickname: 昵称,
682
715
  qqnt: 体检().qqnt,
683
716
  napcat: 体检().napcat,
@@ -687,6 +720,7 @@ export function apply(ctx, 配置) {
687
720
  sessionId: c.sessionId,
688
721
  everyMinMin: Number(c.proactiveEveryMinMin ?? 45),
689
722
  everyMinMax: Number(c.proactiveEveryMinMax ?? c.proactiveEveryMin ?? 90),
723
+ persona: String(c.persona ?? ''),
690
724
  qzoneAuto: Boolean(c.qzoneAuto),
691
725
  qzonePrompt: String(c.qzonePrompt ?? ''),
692
726
  qzoneMinMin: Number(c.qzoneEveryMinMin ?? 120),
@@ -726,6 +760,7 @@ export function apply(ctx, 配置) {
726
760
  if (Number.isFinite(想要.everyMinMax)) patch.proactiveEveryMinMax = Math.max(5, Math.round(Number(想要.everyMinMax)));
727
761
  if (Number.isFinite(想要.perHour)) patch.perHour = Math.min(200, Math.max(1, Math.round(Number(想要.perHour))));
728
762
  if (Number.isFinite(想要.perDay)) patch.perDay = Math.min(1000, Math.max(1, Math.round(Number(想要.perDay))));
763
+ if (typeof 想要.persona === 'string') patch.persona = 想要.persona.slice(0, 2000);
729
764
  if (typeof 想要.qzoneAuto === 'boolean') patch.qzoneAuto = 想要.qzoneAuto;
730
765
  if (typeof 想要.qzonePrompt === 'string') patch.qzonePrompt = 想要.qzonePrompt.slice(0, 2000);
731
766
  if (Number.isFinite(想要.qzoneMinMin)) patch.qzoneEveryMinMin = Math.max(10, Math.round(Number(想要.qzoneMinMin)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-moony",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "把 QQ 小号接进 DeepSeek Harness:收发消息、发表情包、发 QQ 空间说说、参与群聊,能自己找话题,自带启动与限速防封。",
5
5
  "keywords": [
6
6
  "dsh",