dsh-qq-moony 0.7.0 → 0.8.1
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 +14 -0
- package/client.js +55 -2
- package/index.js +159 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 0.8.1 — 2026-10-25
|
|
4
|
+
|
|
5
|
+
- **新增表情库上限** `stickerLimit`(默认 200,设置页可改)。超了淘汰最旧的记录,原图文件不删——库要干净,硬盘不用替它心疼。
|
|
6
|
+
## 0.8.0 — 2026-10-25
|
|
7
|
+
|
|
8
|
+
表情包会自己攒、自己配了;上限多了一个"不限"。
|
|
9
|
+
|
|
10
|
+
- **新增 `qq_tag_sticker` / `qq_pick_sticker`**:收到图会先看一眼,值得收才收进库,打两个标签(mood 情绪 / use 用途 / note 一句描述);回话时按情绪用途挑一张,挑不到就不配。
|
|
11
|
+
- **去重**:按文件 sha1 指纹判断,同一张图转十遍只存一份。
|
|
12
|
+
- **配表情包开关**:`stickerOnReply`(默认开)。关掉就只发字。
|
|
13
|
+
- **自动收录开关**:`autoTagStickers`(默认开)。关掉就不看图、不花那份 token。
|
|
14
|
+
- **不限条数开关**:`limitsUnlimited`(默认关)。开了就绕过每小时/每天上限——给"确实需要"的人留的口子,页面上写着警告。
|
|
15
|
+
|
|
16
|
+
**成本提醒**:自动收录要"看一张图",每条带图的来信都会多一次读图;配表情包也要多一轮工具调用。嫌贵就把这两个开关关掉。
|
|
3
17
|
## 0.7.0 — 2026-10-25
|
|
4
18
|
|
|
5
19
|
把"我自己的东西"从包里拿掉——别人装的是插件,不是我。
|
package/client.js
CHANGED
|
@@ -109,7 +109,8 @@ window.__ModuleLoader__.load({
|
|
|
109
109
|
qzoneMinMin: 状态?.qzoneMinMin ?? 120,
|
|
110
110
|
qzoneMinMax: 状态?.qzoneMinMax ?? 240,
|
|
111
111
|
qzonePrompt: 状态?.qzonePrompt ?? '',
|
|
112
|
-
persona: 状态?.persona ?? '',
|
|
112
|
+
persona: 状态?.persona ?? '',
|
|
113
|
+
stickerLimit: 状态?.stickerLimit ?? 200,
|
|
113
114
|
groupWhitelist: (状态?.groupWhitelist ?? []).join(' '),
|
|
114
115
|
groupContextSize: 状态?.groupContextSize ?? 30,
|
|
115
116
|
groupEveryMinMin: 状态?.groupEveryMinMin ?? 8,
|
|
@@ -140,7 +141,8 @@ window.__ModuleLoader__.load({
|
|
|
140
141
|
qzoneMinMin: Number(值.qzoneMinMin),
|
|
141
142
|
qzoneMinMax: Number(值.qzoneMinMax),
|
|
142
143
|
qzonePrompt: String(值.qzonePrompt ?? ''),
|
|
143
|
-
persona: String(值.persona ?? ''),
|
|
144
|
+
persona: String(值.persona ?? ''),
|
|
145
|
+
stickerLimit: Number(值.stickerLimit),
|
|
144
146
|
groupWhitelist: String(值.groupWhitelist ?? ''),
|
|
145
147
|
groupContextSize: Number(值.groupContextSize),
|
|
146
148
|
groupEveryMinMin: Number(值.groupEveryMinMin),
|
|
@@ -471,6 +473,57 @@ window.__ModuleLoader__.load({
|
|
|
471
473
|
),
|
|
472
474
|
h(开关, { 开: 状态.qzoneAuto, 点: () => 改({ qzoneAuto: !状态.qzoneAuto }) }),
|
|
473
475
|
),
|
|
476
|
+
h(
|
|
477
|
+
'div',
|
|
478
|
+
{ style: { ...卡片 } },
|
|
479
|
+
h(
|
|
480
|
+
'div',
|
|
481
|
+
null,
|
|
482
|
+
h('div', { style: 标题 }, '不限条数'),
|
|
483
|
+
h('div', { style: 说明 }, '关掉每小时/每天的上限。⚠️ 只在确实需要时开——额度是防封的第一道闸'),
|
|
484
|
+
),
|
|
485
|
+
h(开关, { 开: 状态.limitsUnlimited, 点: () => 改({ limitsUnlimited: !状态.limitsUnlimited }) }),
|
|
486
|
+
),
|
|
487
|
+
h(
|
|
488
|
+
'div',
|
|
489
|
+
{ style: { ...卡片 } },
|
|
490
|
+
h(
|
|
491
|
+
'div',
|
|
492
|
+
null,
|
|
493
|
+
h('div', { style: 标题 }, '配表情包'),
|
|
494
|
+
h('div', { style: 说明 }, '回话时如果合适,自己从库里挑一张一起发'),
|
|
495
|
+
),
|
|
496
|
+
h(开关, { 开: 状态.stickerOnReply, 点: () => 改({ stickerOnReply: !状态.stickerOnReply }) }),
|
|
497
|
+
),
|
|
498
|
+
h(
|
|
499
|
+
'div',
|
|
500
|
+
{ style: { ...卡片 } },
|
|
501
|
+
h(
|
|
502
|
+
'div',
|
|
503
|
+
null,
|
|
504
|
+
h('div', { style: 标题 }, '自动收录表情包'),
|
|
505
|
+
h('div', { style: 说明 }, '收到图会看一眼,值得收才收(重复、糊的、真人照片不收)'),
|
|
506
|
+
),
|
|
507
|
+
h(开关, { 开: 状态.autoTagStickers, 点: () => 改({ autoTagStickers: !状态.autoTagStickers }) }),
|
|
508
|
+
),
|
|
509
|
+
h(
|
|
510
|
+
'div',
|
|
511
|
+
{ style: { ...卡片 } },
|
|
512
|
+
h(
|
|
513
|
+
'div',
|
|
514
|
+
null,
|
|
515
|
+
h('div', { style: 标题 }, '表情库上限'),
|
|
516
|
+
h('div', { style: 说明 }, `现在存了 ${状态.stickerCount ?? 0} 张。超过上限时淘汰最旧的记录(原文件不删)`),
|
|
517
|
+
),
|
|
518
|
+
h('input', {
|
|
519
|
+
type: 'number',
|
|
520
|
+
min: 1,
|
|
521
|
+
max: 2000,
|
|
522
|
+
value: 值.stickerLimit,
|
|
523
|
+
onChange: (e) => 数字('stickerLimit', Number(e.target.value)),
|
|
524
|
+
style: 输入框,
|
|
525
|
+
}),
|
|
526
|
+
),
|
|
474
527
|
h(
|
|
475
528
|
'div',
|
|
476
529
|
{ style: { display: 'flex', justifyContent: 'flex-end', gap: '10px', alignItems: 'center' } },
|
package/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* 本插件只当“手”,不当“腿”。
|
|
11
11
|
*/
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
13
14
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
14
15
|
import { homedir } from 'node:os';
|
|
15
16
|
import { join } from 'node:path';
|
|
@@ -60,6 +61,14 @@ const 默认配置 = {
|
|
|
60
61
|
qzonePrompt: '',
|
|
61
62
|
qzoneEveryMinMin: 120,
|
|
62
63
|
qzoneEveryMinMax: 240,
|
|
64
|
+
// 发完消息要不要考虑配表情包
|
|
65
|
+
stickerOnReply: true,
|
|
66
|
+
// 表情库最多存几张(超了淘汰最旧的记录,不动原文件)
|
|
67
|
+
stickerLimit: 200,
|
|
68
|
+
// 收到图要不要鉴定一下、收进表情库
|
|
69
|
+
autoTagStickers: true,
|
|
70
|
+
// 上限开关:true = 不限条数(有些场景确实需要)
|
|
71
|
+
limitsUnlimited: false,
|
|
63
72
|
// 人设:留空则用中性说明;填了就把这段当作"你是谁"交给会话
|
|
64
73
|
persona: '',
|
|
65
74
|
// DSH 启动时自动把 NapCat 拉起来(免扫码,用 -q 快速登录)
|
|
@@ -103,6 +112,36 @@ function 记账(账本, 目标, 摘要) {
|
|
|
103
112
|
appendFileSync(账本, `${Date.now() / 1000}\t${目标}\t${String(摘要).slice(0, 60).replace(/\s+/g, ' ')}\n`, 'utf8');
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
/** 表情包库:<home>/stickers.json,按标签存,按标签取。 */
|
|
116
|
+
function 表情库文件() {
|
|
117
|
+
return join(家目录(), 'stickers.json');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function 读表情库() {
|
|
121
|
+
try {
|
|
122
|
+
const 文件 = 表情库文件();
|
|
123
|
+
if (!existsSync(文件)) return [];
|
|
124
|
+
const 体 = JSON.parse(readFileSync(文件, 'utf8').replace(/^\uFEFF/, ''));
|
|
125
|
+
return Array.isArray(体) ? 体 : [];
|
|
126
|
+
} catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function 写表情库(列表) {
|
|
132
|
+
mkdirSync(家目录(), { recursive: true });
|
|
133
|
+
writeFileSync(表情库文件(), JSON.stringify(列表, null, 2), 'utf8');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function 文件指纹(路径) {
|
|
137
|
+
try {
|
|
138
|
+
const 数据 = readFileSync(路径);
|
|
139
|
+
return createHash('sha1').update(数据).digest('hex').slice(0, 16);
|
|
140
|
+
} catch {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
106
145
|
const 工具输出 = (字段, 渲染) => ({
|
|
107
146
|
schema: {
|
|
108
147
|
type: 'object',
|
|
@@ -291,6 +330,19 @@ export function apply(ctx, 配置) {
|
|
|
291
330
|
return `${人设}\n(同一个工作区、同一份记忆,memory/ 下的文件随时可看。)`;
|
|
292
331
|
}
|
|
293
332
|
|
|
333
|
+
/** 收到图之后:值不值得收进表情库,让它自己判断。 */
|
|
334
|
+
function 鉴定表情包(记录) {
|
|
335
|
+
if (c.autoTagStickers === false) return;
|
|
336
|
+
const 图 = (记录.attachments ?? []).filter((a) => a.path).map((a) => a.path);
|
|
337
|
+
if (图.length === 0) return;
|
|
338
|
+
const 清单 = 图.map((p) => `- ${p}`).join('\n');
|
|
339
|
+
投递到会话(
|
|
340
|
+
`【表情包鉴定】${记录.name} 刚发来 ${图.length} 张图:\n${清单}\n\n` +
|
|
341
|
+
`用 read_image 看一眼。**值得收**就用 qq_tag_sticker 打两个标签(mood 一个词:笑/无语/惊讶/得意…;use 一个词:接梗/收尾/耍赖/安慰…),note 写一句"这图长什么样",方便以后匹配。\n` +
|
|
342
|
+
`不值得(重复的、糊的、没意思的、涉及真人的照片)就什么都不做,直接结束。别为了凑数收。`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
294
346
|
/** 把来信投给 QQ 专用会话,让那边的 agent 去回。失败只记日志,不影响收消息。 */
|
|
295
347
|
function 自动回复(记录) {
|
|
296
348
|
记调试(
|
|
@@ -331,7 +383,10 @@ export function apply(ctx, 配置) {
|
|
|
331
383
|
const 提示 =
|
|
332
384
|
`【QQ ${是群 ? `群 ${记录.group} 里被点名` : '来信'}】${记录.name}(${记录.from})在 ${记录.t} 说:${描述}\n\n` +
|
|
333
385
|
`${身份说明()}\n` +
|
|
334
|
-
`用 qq_send 回给 ${回哪里}${是群 ? '(群里只回一句,别接着聊)' : ',一句就够'}
|
|
386
|
+
`用 qq_send 回给 ${回哪里}${是群 ? '(群里只回一句,别接着聊)' : ',一句就够'},短,像平时说话那样,别解释你在做什么。回完就停。` +
|
|
387
|
+
(c.stickerOnReply === false
|
|
388
|
+
? ''
|
|
389
|
+
: `\n如果这句适合配一张图,可以先用 qq_pick_sticker 按情绪/用途挑一张;挑到了就用 qq_send 的 image 发出去,挑不到就别配——一张不配比配错好。`);
|
|
335
390
|
const 等待 = c.replyDelayMinMs + Math.random() * Math.max(0, c.replyDelayMaxMs - c.replyDelayMinMs);
|
|
336
391
|
const 记忆 = 读记忆片段();
|
|
337
392
|
记调试(`收到来信,${Math.round(等待)}ms 后投给 QQ 专用会话 ${c.sessionId}(记忆 ${记忆.length} 字)`);
|
|
@@ -528,6 +583,7 @@ export function apply(ctx, 配置) {
|
|
|
528
583
|
记一条(记录);
|
|
529
584
|
记群消息(记录);
|
|
530
585
|
自动回复(记录);
|
|
586
|
+
鉴定表情包(记录);
|
|
531
587
|
} catch (e) {
|
|
532
588
|
记调试(`处理消息时出错:${e?.message ?? e}`);
|
|
533
589
|
}
|
|
@@ -720,7 +776,7 @@ export function apply(ctx, 配置) {
|
|
|
720
776
|
sessionId: c.sessionId,
|
|
721
777
|
everyMinMin: Number(c.proactiveEveryMinMin ?? 45),
|
|
722
778
|
everyMinMax: Number(c.proactiveEveryMinMax ?? c.proactiveEveryMin ?? 90),
|
|
723
|
-
persona: String(c.persona ?? ''),
|
|
779
|
+
persona: String(c.persona ?? ''),
|
|
724
780
|
qzoneAuto: Boolean(c.qzoneAuto),
|
|
725
781
|
qzonePrompt: String(c.qzonePrompt ?? ''),
|
|
726
782
|
qzoneMinMin: Number(c.qzoneEveryMinMin ?? 120),
|
|
@@ -734,6 +790,11 @@ export function apply(ctx, 配置) {
|
|
|
734
790
|
groupActiveWindowMin: Number(c.groupActiveWindowMin ?? 5),
|
|
735
791
|
groupCooldownMin: Number(c.groupCooldownMin ?? 10),
|
|
736
792
|
groupPerDay: Number(c.groupPerDay ?? 20),
|
|
793
|
+
limitsUnlimited: c.limitsUnlimited === true,
|
|
794
|
+
stickerOnReply: c.stickerOnReply !== false,
|
|
795
|
+
autoTagStickers: c.autoTagStickers !== false,
|
|
796
|
+
stickerLimit: Number(c.stickerLimit ?? 200),
|
|
797
|
+
stickerCount: 读表情库().length,
|
|
737
798
|
perHour: Number(c.perHour ?? 20),
|
|
738
799
|
perDay: Number(c.perDay ?? 60),
|
|
739
800
|
sentToday: 记录.filter((t) => 现在 - t < 86400).length,
|
|
@@ -760,7 +821,11 @@ export function apply(ctx, 配置) {
|
|
|
760
821
|
if (Number.isFinite(想要.everyMinMax)) patch.proactiveEveryMinMax = Math.max(5, Math.round(Number(想要.everyMinMax)));
|
|
761
822
|
if (Number.isFinite(想要.perHour)) patch.perHour = Math.min(200, Math.max(1, Math.round(Number(想要.perHour))));
|
|
762
823
|
if (Number.isFinite(想要.perDay)) patch.perDay = Math.min(1000, Math.max(1, Math.round(Number(想要.perDay))));
|
|
763
|
-
if (typeof 想要.
|
|
824
|
+
if (typeof 想要.limitsUnlimited === 'boolean') patch.limitsUnlimited = 想要.limitsUnlimited;
|
|
825
|
+
if (typeof 想要.stickerOnReply === 'boolean') patch.stickerOnReply = 想要.stickerOnReply;
|
|
826
|
+
if (typeof 想要.autoTagStickers === 'boolean') patch.autoTagStickers = 想要.autoTagStickers;
|
|
827
|
+
if (Number.isFinite(想要.stickerLimit)) patch.stickerLimit = Math.min(2000, Math.max(1, Math.round(Number(想要.stickerLimit))));
|
|
828
|
+
if (typeof 想要.persona === 'string') patch.persona = 想要.persona.slice(0, 2000);
|
|
764
829
|
if (typeof 想要.qzoneAuto === 'boolean') patch.qzoneAuto = 想要.qzoneAuto;
|
|
765
830
|
if (typeof 想要.qzonePrompt === 'string') patch.qzonePrompt = 想要.qzonePrompt.slice(0, 2000);
|
|
766
831
|
if (Number.isFinite(想要.qzoneMinMin)) patch.qzoneEveryMinMin = Math.max(10, Math.round(Number(想要.qzoneMinMin)));
|
|
@@ -837,6 +902,7 @@ export function apply(ctx, 配置) {
|
|
|
837
902
|
}
|
|
838
903
|
|
|
839
904
|
function 查额度() {
|
|
905
|
+
if (c.limitsUnlimited === true) return '';
|
|
840
906
|
const 现在 = Date.now() / 1000;
|
|
841
907
|
const 记录 = 读账(账本);
|
|
842
908
|
const 一小时 = 记录.filter((t) => 现在 - t < 3600).length;
|
|
@@ -987,7 +1053,7 @@ export function apply(ctx, 配置) {
|
|
|
987
1053
|
ctx.tools.register({
|
|
988
1054
|
name: 'qq_stickers',
|
|
989
1055
|
description:
|
|
990
|
-
'
|
|
1056
|
+
'列出手里攒着的图,带标签(mood/use/note)。拿到路径就能用 qq_send 的 image 发出去。',
|
|
991
1057
|
parameters: {
|
|
992
1058
|
type: 'object',
|
|
993
1059
|
properties: {
|
|
@@ -1003,6 +1069,15 @@ export function apply(ctx, 配置) {
|
|
|
1003
1069
|
async execute(args) {
|
|
1004
1070
|
const 数量 = Math.min(Math.max(Number(args?.count ?? 10), 1), 50);
|
|
1005
1071
|
try {
|
|
1072
|
+
const 库 = 读表情库();
|
|
1073
|
+
if (库.length > 0) {
|
|
1074
|
+
const 行 = 库
|
|
1075
|
+
.slice(-数量)
|
|
1076
|
+
.reverse()
|
|
1077
|
+
.map((x) => `[${x.mood ?? '?'}/${x.use ?? '?'}] ${x.note ? x.note + '|' : ''}${x.path}`);
|
|
1078
|
+
return { ok: true, lines: 行 };
|
|
1079
|
+
}
|
|
1080
|
+
// 还没打标签的,至少把文件列出来
|
|
1006
1081
|
const 目录 = join(家目录(), 'media');
|
|
1007
1082
|
if (!existsSync(目录)) return { ok: true, lines: [] };
|
|
1008
1083
|
const 文件 = readdirSync(目录)
|
|
@@ -1022,6 +1097,86 @@ export function apply(ctx, 配置) {
|
|
|
1022
1097
|
}),
|
|
1023
1098
|
);
|
|
1024
1099
|
|
|
1100
|
+
// ---------- qq_tag_sticker ----------
|
|
1101
|
+
ctx.effect(() =>
|
|
1102
|
+
ctx.tools.register({
|
|
1103
|
+
name: 'qq_tag_sticker',
|
|
1104
|
+
description:
|
|
1105
|
+
'把一张图收进自己的表情包库并打标签。值得收才收——重复的、没意思的、看不清的就别收。',
|
|
1106
|
+
parameters: {
|
|
1107
|
+
type: 'object',
|
|
1108
|
+
properties: {
|
|
1109
|
+
path: { type: 'string', description: '图片本地路径' },
|
|
1110
|
+
mood: { type: 'string', description: '情绪:笑 / 敷衍 / 惊讶 / 无语 / 得意 …' },
|
|
1111
|
+
use: { type: 'string', description: '用在哪儿:接梗 / 收尾 / 耍赖 / 安慰 …' },
|
|
1112
|
+
note: { type: 'string', description: '一句话说明这张图长什么样,方便以后匹配' },
|
|
1113
|
+
},
|
|
1114
|
+
required: ['path', 'mood'],
|
|
1115
|
+
additionalProperties: false,
|
|
1116
|
+
},
|
|
1117
|
+
output: 工具输出({ added: { type: 'boolean' } }, (v) =>
|
|
1118
|
+
v.ok ? (v.added ? '收进库了。' : '库里已经有这张,没重复加。') : `没存成:${v.reason ?? '未知原因'}`,
|
|
1119
|
+
),
|
|
1120
|
+
async execute(args) {
|
|
1121
|
+
const 路径 = String(args?.path ?? '').trim();
|
|
1122
|
+
if (!路径 || !existsSync(路径)) return { ok: false, added: false, reason: '找不到这张图' };
|
|
1123
|
+
const 指纹 = 文件指纹(路径);
|
|
1124
|
+
const 库 = 读表情库();
|
|
1125
|
+
if (指纹 && 库.some((x) => x.hash === 指纹)) return { ok: true, added: false };
|
|
1126
|
+
const 上限 = Math.max(1, Number(c.stickerLimit ?? 200));
|
|
1127
|
+
while (库.length >= 上限) {
|
|
1128
|
+
const 淘汰 = 库.shift();
|
|
1129
|
+
记调试(`表情库满(${上限}),淘汰最旧的:${淘汰?.path ?? '?'}`);
|
|
1130
|
+
}
|
|
1131
|
+
const 条目 = {
|
|
1132
|
+
path: 路径,
|
|
1133
|
+
hash: 指纹,
|
|
1134
|
+
mood: String(args?.mood ?? '').slice(0, 20),
|
|
1135
|
+
use: String(args?.use ?? '').slice(0, 20),
|
|
1136
|
+
note: String(args?.note ?? '').slice(0, 80),
|
|
1137
|
+
addedAt: new Date().toISOString(),
|
|
1138
|
+
};
|
|
1139
|
+
库.push(条目);
|
|
1140
|
+
写表情库(库);
|
|
1141
|
+
记调试(`表情入库:${条目.mood}/${条目.use} ${路径}`);
|
|
1142
|
+
return { ok: true, added: true };
|
|
1143
|
+
},
|
|
1144
|
+
}),
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
// ---------- qq_pick_sticker ----------
|
|
1148
|
+
ctx.effect(() =>
|
|
1149
|
+
ctx.tools.register({
|
|
1150
|
+
name: 'qq_pick_sticker',
|
|
1151
|
+
description:
|
|
1152
|
+
'从表情包库里挑一张合适的(按情绪和用途匹配)。没有合适的会明说,别硬凑。',
|
|
1153
|
+
parameters: {
|
|
1154
|
+
type: 'object',
|
|
1155
|
+
properties: {
|
|
1156
|
+
mood: { type: 'string', description: '想要的情绪,比如 笑 / 无语 / 得意' },
|
|
1157
|
+
use: { type: 'string', description: '用在哪,比如 接梗 / 收尾 / 耍赖' },
|
|
1158
|
+
},
|
|
1159
|
+
additionalProperties: false,
|
|
1160
|
+
},
|
|
1161
|
+
output: 工具输出({ path: { type: 'string' } }, (v) =>
|
|
1162
|
+
v.ok ? (v.path ? `挑到这张:${v.path}` : `没挑到合适的:${v.reason ?? '库里没有匹配的'}`) : `挑不出来:${v.reason ?? '未知原因'}`,
|
|
1163
|
+
),
|
|
1164
|
+
async execute(args) {
|
|
1165
|
+
const 库 = 读表情库();
|
|
1166
|
+
if (库.length === 0) return { ok: false, path: '', reason: '表情库还是空的' };
|
|
1167
|
+
const 情绪 = String(args?.mood ?? '').trim();
|
|
1168
|
+
const 用途 = String(args?.use ?? '').trim();
|
|
1169
|
+
const 命中 = 库.filter(
|
|
1170
|
+
(x) => (!情绪 || x.mood === 情绪) && (!用途 || x.use === 用途),
|
|
1171
|
+
);
|
|
1172
|
+
const 池 = 命中.length > 0 ? 命中 : [];
|
|
1173
|
+
if (池.length === 0) return { ok: true, path: '', reason: '库里没有这个组合' };
|
|
1174
|
+
const 选 = 池[Math.floor(Math.random() * 池.length)];
|
|
1175
|
+
return { ok: true, path: 选.path };
|
|
1176
|
+
},
|
|
1177
|
+
}),
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1025
1180
|
// ---------- qq_qzone ----------
|
|
1026
1181
|
ctx.effect(() =>
|
|
1027
1182
|
ctx.tools.register({
|