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 +17 -0
- package/README.md +13 -0
- package/client.js +38 -1
- package/cordis.patch.yml +5 -2
- package/index.js +56 -21
- package/package.json +1 -1
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 (状态.
|
|
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: '
|
|
10
|
-
uin:
|
|
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
|
-
/**
|
|
137
|
-
async function
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
+
/* 不是 JSON,多半是别的服务 */
|
|
146
149
|
}
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
(要求 ?
|
|
644
|
-
`想发就用 qq_qzone
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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)));
|