koishi-plugin-chatluna-toolbox 0.0.12 → 0.0.15

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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/lib/index.js +489 -39
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -13,10 +13,14 @@
13
13
  ### 1) 原生工具
14
14
  - 戳一戳
15
15
  - 设置自身资料
16
+ - 设置 QQ 头像
16
17
  - 设置群名片
18
+ - 设置群成员专属头衔
17
19
  - 给消息添加表情
18
20
  - 撤回消息
19
21
 
22
+ QQ 头像工具会把 HTTP/HTTPS 图片先转为 OneBot `base64://` 文件再提交,避免 OneBot 端无法访问 Koishi 临时图片地址。
23
+
20
24
  ### 2) XML 工具
21
25
  支持从模型输出中解析 XML 动作标签并执行:
22
26
 
package/lib/index.js CHANGED
@@ -201,6 +201,10 @@ var require_lib = __commonJS({
201
201
  writable: true
202
202
  });
203
203
  }
204
+ function createMessageFingerprint(message, response) {
205
+ const type = String(message.type ?? message.role ?? "").trim().toLowerCase();
206
+ return `${type}:${response}`;
207
+ }
204
208
  function restoreDispatcher(key, dispatcher) {
205
209
  if (dispatcher.messages.push === dispatcher.patchedPush) {
206
210
  dispatcher.messages.push = dispatcher.originalPush;
@@ -212,17 +216,22 @@ var require_lib = __commonJS({
212
216
  let dispatcher = getDispatcher(messages, key);
213
217
  if (!dispatcher) {
214
218
  const listeners = /* @__PURE__ */ new Set();
215
- const processedMessages = /* @__PURE__ */ new WeakSet();
219
+ const processedMessages = /* @__PURE__ */ new WeakMap();
216
220
  const originalPush = messages.push;
217
221
  const patchedPush = function patchedPush2(...items) {
218
222
  const result = originalPush.apply(this, items);
219
223
  for (const item of items) {
220
224
  if (!item || typeof item !== "object") continue;
221
- if (processedMessages.has(item)) continue;
222
- processedMessages.add(item);
223
225
  const message = item;
224
226
  const response = extractAssistantText(message);
225
227
  if (!response) continue;
228
+ const fingerprint = createMessageFingerprint(
229
+ item,
230
+ response
231
+ );
232
+ const previousFingerprint = processedMessages.get(item);
233
+ if (previousFingerprint === fingerprint) continue;
234
+ processedMessages.set(item, fingerprint);
226
235
  for (const listener2 of Array.from(listeners)) {
227
236
  try {
228
237
  listener2(message);
@@ -414,8 +423,10 @@ __export(index_exports, {
414
423
  createRandomProvider: () => createRandomProvider,
415
424
  createSetGroupBanTool: () => createSetGroupBanTool,
416
425
  createSetGroupCardTool: () => createSetGroupCardTool,
426
+ createSetGroupSpecialTitleTool: () => createSetGroupSpecialTitleTool,
417
427
  createSetMsgEmojiTool: () => createSetMsgEmojiTool,
418
428
  createSetProfileTool: () => createSetProfileTool,
429
+ createSetQQAvatarTool: () => createSetQQAvatarTool,
419
430
  createUserInfoProvider: () => createUserInfoProvider,
420
431
  createXmlProcessor: () => createXmlProcessor,
421
432
  ensureOneBotSession: () => ensureOneBotSession,
@@ -434,7 +445,9 @@ __export(index_exports, {
434
445
  sendMsgEmoji: () => sendMsgEmoji,
435
446
  sendPoke: () => sendPoke,
436
447
  sendSetGroupCard: () => sendSetGroupCard,
437
- sendSetProfile: () => sendSetProfile
448
+ sendSetGroupSpecialTitle: () => sendSetGroupSpecialTitle,
449
+ sendSetProfile: () => sendSetProfile,
450
+ sendSetQQAvatar: () => sendSetQQAvatar
438
451
  });
439
452
  module.exports = __toCommonJS(index_exports);
440
453
 
@@ -454,8 +467,10 @@ var import_koishi2 = require("koishi");
454
467
  // src/features/native-tools/defaults.ts
455
468
  var DEFAULT_POKE_TOOL_DESCRIPTION = "\u5BF9\u6307\u5B9A\u7528\u6237\u6267\u884C\u6233\u4E00\u6233\u3002";
456
469
  var DEFAULT_SET_SELF_PROFILE_TOOL_DESCRIPTION = "\u4FEE\u6539\u81EA\u5DF1\u7684 QQ \u8D44\u6599\uFF0C\u53EF\u8BBE\u7F6E\u6635\u79F0\u3001\u7B7E\u540D\u548C\u6027\u522B\u3002";
470
+ var DEFAULT_SET_QQ_AVATAR_TOOL_DESCRIPTION = "\u4FEE\u6539\u673A\u5668\u4EBA QQ \u5934\u50CF\u3002\u63D0\u4F9B imageUrl\uFF1BHTTP/HTTPS \u56FE\u7247\u4F1A\u5148\u7531 Koishi \u8BFB\u53D6\u5E76\u8F6C\u4E3A OneBot base64 \u6587\u4EF6\uFF0C\u907F\u514D\u4E34\u65F6 URL \u8FC7\u671F\u6216 OneBot \u7AEF\u65E0\u6CD5\u8BBF\u95EE\u3002";
457
471
  var DEFAULT_SET_GROUP_CARD_TOOL_DESCRIPTION = "\u4FEE\u6539\u7FA4\u6210\u5458\u7684\u7FA4\u6635\u79F0\u3002";
458
472
  var DEFAULT_SET_GROUP_BAN_TOOL_DESCRIPTION = "\u7528\u4E8E\u7BA1\u7406\u7FA4\u6210\u5458\u7981\u8A00\u72B6\u6001\uFF0C\u53EF\u8BBE\u7F6E\u7981\u8A00\u65F6\u957F\u6216\u89E3\u9664\u7981\u8A00\u3002";
473
+ var DEFAULT_SET_GROUP_SPECIAL_TITLE_TOOL_DESCRIPTION = "\u4FEE\u6539\u6216\u6E05\u9664\u7FA4\u6210\u5458\u7684 QQ \u7FA4\u4E13\u5C5E\u5934\u8854\u3002";
459
474
  var DEFAULT_SET_MSG_EMOJI_TOOL_DESCRIPTION = "\u5BF9\u6307\u5B9A\u6D88\u606F\u6DFB\u52A0\u8868\u60C5\u56DE\u5E94\u3002";
460
475
  var DEFAULT_DELETE_MESSAGE_TOOL_DESCRIPTION = "\u6839\u636E\u6D88\u606F ID \u64A4\u56DE\u6307\u5B9A\u6D88\u606F\u3002";
461
476
 
@@ -473,6 +488,11 @@ var NativeToolsSchema = import_koishi2.Schema.object({
473
488
  toolName: import_koishi2.Schema.string().default("set_self_profile").description("\u5DE5\u5177\u540D\u79F0"),
474
489
  description: import_koishi2.Schema.string().default(DEFAULT_SET_SELF_PROFILE_TOOL_DESCRIPTION).description("\u5DE5\u5177\u63CF\u8FF0")
475
490
  }).description("\u4FEE\u6539\u81EA\u8EAB\u8D26\u6237\u4FE1\u606F\u5DE5\u5177").collapse(),
491
+ setQQAvatar: import_koishi2.Schema.object({
492
+ enabled: import_koishi2.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u4FEE\u6539\u673A\u5668\u4EBA QQ \u5934\u50CF"),
493
+ toolName: import_koishi2.Schema.string().default("set_qq_avatar").description("\u5DE5\u5177\u540D\u79F0"),
494
+ description: import_koishi2.Schema.string().default(DEFAULT_SET_QQ_AVATAR_TOOL_DESCRIPTION).description("\u5DE5\u5177\u63CF\u8FF0")
495
+ }).description("\u4FEE\u6539 QQ \u5934\u50CF\u5DE5\u5177").collapse(),
476
496
  setGroupCard: import_koishi2.Schema.object({
477
497
  enabled: import_koishi2.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u4FEE\u6539\u7FA4\u6210\u5458\u6635\u79F0"),
478
498
  toolName: import_koishi2.Schema.string().default("set_group_card").description("\u5DE5\u5177\u540D\u79F0"),
@@ -483,6 +503,11 @@ var NativeToolsSchema = import_koishi2.Schema.object({
483
503
  toolName: import_koishi2.Schema.string().default("set_group_ban").description("\u5DE5\u5177\u540D\u79F0"),
484
504
  description: import_koishi2.Schema.string().default(DEFAULT_SET_GROUP_BAN_TOOL_DESCRIPTION).description("\u5DE5\u5177\u63CF\u8FF0")
485
505
  }).description("\u7981\u8A00\u5DE5\u5177").collapse(),
506
+ setGroupSpecialTitle: import_koishi2.Schema.object({
507
+ enabled: import_koishi2.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u4FEE\u6539\u7FA4\u6210\u5458\u4E13\u5C5E\u5934\u8854"),
508
+ toolName: import_koishi2.Schema.string().default("set_group_special_title").description("\u5DE5\u5177\u540D\u79F0"),
509
+ description: import_koishi2.Schema.string().default(DEFAULT_SET_GROUP_SPECIAL_TITLE_TOOL_DESCRIPTION).description("\u5DE5\u5177\u63CF\u8FF0")
510
+ }).description("\u4FEE\u6539\u7FA4\u6210\u5458\u4E13\u5C5E\u5934\u8854\u5DE5\u5177").collapse(),
486
511
  setMsgEmoji: import_koishi2.Schema.object({
487
512
  enabled: import_koishi2.Schema.boolean().default(false).description(
488
513
  "\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u7ED9\u6D88\u606F\u6DFB\u52A0\u8868\u60C5\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF0C\u8868\u60C5\u5BF9\u7167\u8868\uFF1Ahttps://bot.q.qq.com/wiki/develop/pythonsdk/model/emoji.html \uFF09"
@@ -508,6 +533,11 @@ var NativeToolsSchema = import_koishi2.Schema.object({
508
533
  toolName: "set_self_profile",
509
534
  description: DEFAULT_SET_SELF_PROFILE_TOOL_DESCRIPTION
510
535
  },
536
+ setQQAvatar: {
537
+ enabled: false,
538
+ toolName: "set_qq_avatar",
539
+ description: DEFAULT_SET_QQ_AVATAR_TOOL_DESCRIPTION
540
+ },
511
541
  setGroupCard: {
512
542
  enabled: false,
513
543
  toolName: "set_group_card",
@@ -518,6 +548,11 @@ var NativeToolsSchema = import_koishi2.Schema.object({
518
548
  toolName: "set_group_ban",
519
549
  description: DEFAULT_SET_GROUP_BAN_TOOL_DESCRIPTION
520
550
  },
551
+ setGroupSpecialTitle: {
552
+ enabled: false,
553
+ toolName: "set_group_special_title",
554
+ description: DEFAULT_SET_GROUP_SPECIAL_TITLE_TOOL_DESCRIPTION
555
+ },
521
556
  setMsgEmoji: {
522
557
  enabled: false,
523
558
  toolName: "set_msg_emoji",
@@ -706,7 +741,15 @@ var path = __toESM(require("path"));
706
741
  function createLogger(ctx, config) {
707
742
  const logger = ctx.logger("chatluna-toolbox");
708
743
  return (level, message, meta) => {
709
- if (level === "debug" && !config.debugLogging) return;
744
+ if (level === "debug") {
745
+ if (!config.debugLogging) return;
746
+ if (meta === void 0) {
747
+ logger.info(message);
748
+ return;
749
+ }
750
+ logger.info(message, meta);
751
+ return;
752
+ }
710
753
  if (meta === void 0) {
711
754
  logger[level](message);
712
755
  return;
@@ -997,9 +1040,73 @@ function createSetGroupCardTool(deps) {
997
1040
  }();
998
1041
  }
999
1042
 
1000
- // src/features/native-tools/tools/set-msg-emoji.ts
1043
+ // src/features/native-tools/tools/set-group-special-title.ts
1001
1044
  var import_zod5 = require("zod");
1002
1045
  var import_tools4 = require("@langchain/core/tools");
1046
+ function resolveGroupId2(session, groupId) {
1047
+ return groupId?.trim() || (session.guildId || "").trim() || (session.channelId || "").trim() || (session.roomId || "").trim();
1048
+ }
1049
+ async function sendSetGroupSpecialTitle(params) {
1050
+ try {
1051
+ const { session, userId, groupId, title, log } = params;
1052
+ const {
1053
+ error,
1054
+ internal,
1055
+ session: validatedSession
1056
+ } = ensureOneBotSession(session);
1057
+ if (error) return error;
1058
+ const resolvedGroupId = resolveGroupId2(validatedSession, groupId);
1059
+ if (!resolvedGroupId) {
1060
+ return "Missing groupId. Provide groupId explicitly or run inside a group session.";
1061
+ }
1062
+ const userIdRaw = userId.trim();
1063
+ if (!userIdRaw) return "userId is required.";
1064
+ const titleRaw = title.trim();
1065
+ await callOneBotAPI(
1066
+ internal,
1067
+ "set_group_special_title",
1068
+ {
1069
+ group_id: resolvedGroupId,
1070
+ user_id: userIdRaw,
1071
+ special_title: titleRaw
1072
+ },
1073
+ ["setGroupSpecialTitle"]
1074
+ );
1075
+ const message = titleRaw ? `\u7FA4\u4E13\u5C5E\u5934\u8854\u5DF2\u66F4\u65B0\uFF1A${userIdRaw} -> ${titleRaw}` : `\u7FA4\u4E13\u5C5E\u5934\u8854\u5DF2\u6E05\u9664\uFF1A${userIdRaw}`;
1076
+ log?.("info", message);
1077
+ return message;
1078
+ } catch (error) {
1079
+ params.log?.("warn", "set_group_special_title failed", error);
1080
+ return `set_group_special_title failed: ${error.message}`;
1081
+ }
1082
+ }
1083
+ function createSetGroupSpecialTitleTool(deps) {
1084
+ const { toolName, description, protocol, log } = deps;
1085
+ return new class extends import_tools4.StructuredTool {
1086
+ name = toolName || "set_group_special_title";
1087
+ description = description || DEFAULT_SET_GROUP_SPECIAL_TITLE_TOOL_DESCRIPTION;
1088
+ schema = import_zod5.z.object({
1089
+ groupId: import_zod5.z.string().optional().describe("Target group ID. Defaults to current session group."),
1090
+ userId: import_zod5.z.string().min(1, "userId is required").describe("Target member user ID."),
1091
+ title: import_zod5.z.string().describe("New special title. Use an empty string to clear it.")
1092
+ });
1093
+ async _call(input, _manager, runnable) {
1094
+ const session = getSession(runnable);
1095
+ return sendSetGroupSpecialTitle({
1096
+ session,
1097
+ userId: input.userId,
1098
+ title: input.title,
1099
+ groupId: input.groupId,
1100
+ protocol,
1101
+ log
1102
+ });
1103
+ }
1104
+ }();
1105
+ }
1106
+
1107
+ // src/features/native-tools/tools/set-msg-emoji.ts
1108
+ var import_zod6 = require("zod");
1109
+ var import_tools5 = require("@langchain/core/tools");
1003
1110
  async function sendMsgEmoji(params) {
1004
1111
  try {
1005
1112
  const { session, messageId, emojiId, log, protocol } = params;
@@ -1030,12 +1137,12 @@ async function sendMsgEmoji(params) {
1030
1137
  }
1031
1138
  function createSetMsgEmojiTool(deps) {
1032
1139
  const { toolName, description, log, protocol } = deps;
1033
- return new class extends import_tools4.StructuredTool {
1140
+ return new class extends import_tools5.StructuredTool {
1034
1141
  name = toolName || "set_msg_emoji";
1035
1142
  description = description || DEFAULT_SET_MSG_EMOJI_TOOL_DESCRIPTION;
1036
- schema = import_zod5.z.object({
1037
- messageId: import_zod5.z.string().min(1, "message_id is required").describe("Target message ID."),
1038
- emojiId: import_zod5.z.string().min(1, "emoji_id is required").describe("Emoji ID to send.")
1143
+ schema = import_zod6.z.object({
1144
+ messageId: import_zod6.z.string().min(1, "message_id is required").describe("Target message ID."),
1145
+ emojiId: import_zod6.z.string().min(1, "emoji_id is required").describe("Emoji ID to send.")
1039
1146
  });
1040
1147
  async _call(input, _manager, runnable) {
1041
1148
  const session = getSession(runnable);
@@ -1051,8 +1158,8 @@ function createSetMsgEmojiTool(deps) {
1051
1158
  }
1052
1159
 
1053
1160
  // src/features/native-tools/tools/profile.ts
1054
- var import_zod6 = require("zod");
1055
- var import_tools5 = require("@langchain/core/tools");
1161
+ var import_zod7 = require("zod");
1162
+ var import_tools6 = require("@langchain/core/tools");
1056
1163
  var genders = {
1057
1164
  unknown: "0",
1058
1165
  male: "1",
@@ -1079,13 +1186,13 @@ async function sendSetProfile(params) {
1079
1186
  }
1080
1187
  function createSetProfileTool(deps) {
1081
1188
  const { toolName, description, log, protocol } = deps;
1082
- return new class extends import_tools5.StructuredTool {
1189
+ return new class extends import_tools6.StructuredTool {
1083
1190
  name = toolName || "set_self_profile";
1084
1191
  description = description || DEFAULT_SET_SELF_PROFILE_TOOL_DESCRIPTION;
1085
- schema = import_zod6.z.object({
1086
- nickname: import_zod6.z.string().min(1, "nickname is required").describe("The new nickname for the bot."),
1087
- signature: import_zod6.z.string().optional().describe("Optional: the new personal signature."),
1088
- gender: import_zod6.z.enum(["unknown", "male", "female"]).optional().describe("Optional: the new gender.")
1192
+ schema = import_zod7.z.object({
1193
+ nickname: import_zod7.z.string().min(1, "nickname is required").describe("The new nickname for the bot."),
1194
+ signature: import_zod7.z.string().optional().describe("Optional: the new personal signature."),
1195
+ gender: import_zod7.z.enum(["unknown", "male", "female"]).optional().describe("Optional: the new gender.")
1089
1196
  });
1090
1197
  async _call(input, _manager, runnable) {
1091
1198
  return sendSetProfile({
@@ -1100,6 +1207,84 @@ function createSetProfileTool(deps) {
1100
1207
  }();
1101
1208
  }
1102
1209
 
1210
+ // src/features/native-tools/tools/set-qq-avatar.ts
1211
+ var import_zod8 = require("zod");
1212
+ var import_tools7 = require("@langchain/core/tools");
1213
+ function isHttpUrl(value) {
1214
+ return /^https?:\/\//i.test(value);
1215
+ }
1216
+ function toBuffer(data) {
1217
+ if (Buffer.isBuffer(data)) return data;
1218
+ if (data instanceof ArrayBuffer) return Buffer.from(data);
1219
+ if (ArrayBuffer.isView(data)) {
1220
+ return Buffer.from(data.buffer, data.byteOffset, data.byteLength);
1221
+ }
1222
+ if (typeof data === "string") return Buffer.from(data);
1223
+ throw new Error("unsupported image response body.");
1224
+ }
1225
+ async function fetchImageAsOneBotFile(ctx, imageUrl) {
1226
+ if (!isHttpUrl(imageUrl)) return imageUrl;
1227
+ try {
1228
+ const http = ctx?.http;
1229
+ const data = typeof http?.get === "function" ? await http.get(imageUrl, { responseType: "arraybuffer" }) : await fetch(imageUrl).then(async (response) => {
1230
+ if (!response.ok) {
1231
+ throw new Error(`HTTP ${response.status}`);
1232
+ }
1233
+ return response.arrayBuffer();
1234
+ });
1235
+ return `base64://${toBuffer(data).toString("base64")}`;
1236
+ } catch (error) {
1237
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u5934\u50CF\u56FE\u7247\uFF1A${error.message}`);
1238
+ }
1239
+ }
1240
+ function assertOneBotSuccess(result) {
1241
+ const response = result;
1242
+ if (!response || typeof response !== "object") return;
1243
+ const failed = response.status === "failed" || typeof response.retcode === "number" && response.retcode !== 0;
1244
+ if (!failed) return;
1245
+ const reason = response.wording || response.message || response.status;
1246
+ const retcode = typeof response.retcode === "number" ? ` (retcode: ${response.retcode})` : "";
1247
+ throw new Error(`${reason || "OneBot returned failed"}${retcode}`);
1248
+ }
1249
+ async function sendSetQQAvatar(params) {
1250
+ const { ctx, session, log } = params;
1251
+ try {
1252
+ const imageUrl = params.imageUrl.trim();
1253
+ if (!imageUrl) return "imageUrl is required.";
1254
+ const { error, internal } = ensureOneBotSession(session);
1255
+ if (error) return error;
1256
+ const file = await fetchImageAsOneBotFile(ctx, imageUrl);
1257
+ const result = await callOneBotAPI(internal, "set_qq_avatar", { file }, [
1258
+ "setQQAvatar"
1259
+ ]);
1260
+ assertOneBotSuccess(result);
1261
+ const message = "QQ \u5934\u50CF\u5DF2\u66F4\u65B0\u3002";
1262
+ log?.("info", message);
1263
+ return message;
1264
+ } catch (error) {
1265
+ log?.("warn", "set_qq_avatar failed", error);
1266
+ return `set_qq_avatar failed: ${error.message}`;
1267
+ }
1268
+ }
1269
+ function createSetQQAvatarTool(deps) {
1270
+ const { ctx, toolName, description, log } = deps;
1271
+ return new class extends import_tools7.StructuredTool {
1272
+ name = toolName || "set_qq_avatar";
1273
+ description = description || DEFAULT_SET_QQ_AVATAR_TOOL_DESCRIPTION;
1274
+ schema = import_zod8.z.object({
1275
+ imageUrl: import_zod8.z.string().min(1, "imageUrl is required").describe("Image URL or local file path readable by the OneBot side.")
1276
+ });
1277
+ async _call(input, _manager, runnable) {
1278
+ return sendSetQQAvatar({
1279
+ session: getSession(runnable),
1280
+ imageUrl: input.imageUrl,
1281
+ ctx,
1282
+ log
1283
+ });
1284
+ }
1285
+ }();
1286
+ }
1287
+
1103
1288
  // src/features/native-tools/register.ts
1104
1289
  var NATIVE_TOOL_DEFAULT_AVAILABILITY = {
1105
1290
  enabled: true,
@@ -1168,6 +1353,24 @@ function registerNativeTools(deps) {
1168
1353
  });
1169
1354
  log?.("info", `\u8BBE\u7F6E\u8D44\u6599\u5DE5\u5177\u5DF2\u6CE8\u518C: ${toolName}`);
1170
1355
  }
1356
+ if (config.setQQAvatar.enabled) {
1357
+ const toolName = resolveToolName(
1358
+ config.setQQAvatar.toolName,
1359
+ "set_qq_avatar"
1360
+ );
1361
+ const description = resolveToolDescription(
1362
+ config.setQQAvatar.description,
1363
+ DEFAULT_SET_QQ_AVATAR_TOOL_DESCRIPTION
1364
+ );
1365
+ plugin.registerTool(toolName, {
1366
+ selector: () => true,
1367
+ authorization: (session) => session?.platform === "onebot",
1368
+ description,
1369
+ createTool: () => createSetQQAvatarTool({ ctx, toolName, description, log }),
1370
+ meta: createNativeToolMeta("onebot", ["profile"])
1371
+ });
1372
+ log?.("info", `QQ \u5934\u50CF\u5DE5\u5177\u5DF2\u6CE8\u518C: ${toolName}`);
1373
+ }
1171
1374
  if (config.setGroupCard.enabled) {
1172
1375
  const toolName = resolveToolName(
1173
1376
  config.setGroupCard.toolName,
@@ -1204,6 +1407,29 @@ function registerNativeTools(deps) {
1204
1407
  });
1205
1408
  log?.("info", `\u7FA4\u6210\u5458\u7981\u8A00\u5DE5\u5177\u5DF2\u6CE8\u518C: ${toolName}`);
1206
1409
  }
1410
+ if (config.setGroupSpecialTitle.enabled) {
1411
+ const toolName = resolveToolName(
1412
+ config.setGroupSpecialTitle.toolName,
1413
+ "set_group_special_title"
1414
+ );
1415
+ const description = resolveToolDescription(
1416
+ config.setGroupSpecialTitle.description,
1417
+ DEFAULT_SET_GROUP_SPECIAL_TITLE_TOOL_DESCRIPTION
1418
+ );
1419
+ plugin.registerTool(toolName, {
1420
+ selector: () => true,
1421
+ authorization: (session) => session?.platform === "onebot",
1422
+ description,
1423
+ createTool: () => createSetGroupSpecialTitleTool({
1424
+ toolName,
1425
+ description,
1426
+ log,
1427
+ protocol
1428
+ }),
1429
+ meta: createNativeToolMeta("onebot", ["group"])
1430
+ });
1431
+ log?.("info", `\u7FA4\u4E13\u5C5E\u5934\u8854\u5DE5\u5177\u5DF2\u6CE8\u518C: ${toolName}`);
1432
+ }
1207
1433
  if (config.setMsgEmoji.enabled) {
1208
1434
  const toolName = resolveToolName(
1209
1435
  config.setMsgEmoji.toolName,
@@ -1980,6 +2206,9 @@ function createGroupInfoProvider(deps) {
1980
2206
  }
1981
2207
 
1982
2208
  // src/features/variables/providers/group-shut-list.ts
2209
+ var GROUP_SHUT_LIST_TIMEOUT_MS = 200;
2210
+ var GROUP_SHUT_LIST_TIMEOUT_ERROR = "\u8BF7\u6C42\u7FA4\u7981\u8A00\u5217\u8868\u8D85\u65F6\u3002";
2211
+ var GROUP_SHUT_LIST_CACHE_TTL_MS = 15e3;
1983
2212
  function normalizeTimestamp2(raw) {
1984
2213
  if (raw === null || raw === void 0 || raw === "" || Number(raw) === 0)
1985
2214
  return null;
@@ -2001,13 +2230,6 @@ function pickFirst2(...values) {
2001
2230
  }
2002
2231
  return "";
2003
2232
  }
2004
- function extractList(result) {
2005
- if (Array.isArray(result)) return result;
2006
- if (result && typeof result === "object" && Array.isArray(result.data)) {
2007
- return result.data;
2008
- }
2009
- return [];
2010
- }
2011
2233
  function normalizeShutItem(item) {
2012
2234
  const userId = pickFirst2(
2013
2235
  item.uin,
@@ -2035,32 +2257,227 @@ function normalizeShutItem(item) {
2035
2257
  shutUpTimeText
2036
2258
  };
2037
2259
  }
2038
- async function fetchGroupShutList(session, config) {
2260
+ function previewText(text, max = 180) {
2261
+ const raw = String(text ?? "").trim();
2262
+ if (!raw) return "";
2263
+ return raw.length > max ? `${raw.slice(0, max)}...` : raw;
2264
+ }
2265
+ function getDataKind(data) {
2266
+ if (Array.isArray(data)) return "array";
2267
+ if (data === null) return "null";
2268
+ if (data === void 0) return "undefined";
2269
+ return typeof data;
2270
+ }
2271
+ function debug(log, message, meta) {
2272
+ log?.("debug", message, meta);
2273
+ }
2274
+ function debugErrorMessage(error) {
2275
+ if (error instanceof Error) return previewText(error.message);
2276
+ return previewText(error);
2277
+ }
2278
+ function withTiming(promise) {
2279
+ const startedAt = Date.now();
2280
+ return promise.then((value) => ({
2281
+ value,
2282
+ elapsedMs: Date.now() - startedAt
2283
+ }));
2284
+ }
2285
+ function withTimeout(promise, timeoutMs) {
2286
+ return new Promise((resolve2, reject) => {
2287
+ const timer = setTimeout(() => {
2288
+ reject(new Error(GROUP_SHUT_LIST_TIMEOUT_ERROR));
2289
+ }, timeoutMs);
2290
+ promise.then((value) => {
2291
+ clearTimeout(timer);
2292
+ resolve2(value);
2293
+ }).catch((error) => {
2294
+ clearTimeout(timer);
2295
+ reject(error);
2296
+ });
2297
+ });
2298
+ }
2299
+ function isOneBotEnvelope(result) {
2300
+ return !!result && typeof result === "object" && !Array.isArray(result);
2301
+ }
2302
+ function isTimeoutError(error) {
2303
+ return error instanceof Error && error.message === GROUP_SHUT_LIST_TIMEOUT_ERROR;
2304
+ }
2305
+ function normalizeErrorMessage(error) {
2306
+ if (!(error instanceof Error)) return "";
2307
+ return error.message.toLowerCase();
2308
+ }
2309
+ function shouldFallbackByError(error) {
2310
+ if (isTimeoutError(error)) return false;
2311
+ const message = normalizeErrorMessage(error);
2312
+ if (!message) return false;
2313
+ return message.includes("string required") || message.includes("number required") || message.includes("invalid") || message.includes("type") || message.includes("\u53C2\u6570");
2314
+ }
2315
+ function shouldFallbackByEnvelope(envelope) {
2316
+ const message = `${envelope.message || ""} ${envelope.wording || ""}`.toLowerCase().trim();
2317
+ if (!message) return false;
2318
+ return message.includes("string") || message.includes("number") || message.includes("type") || message.includes("\u53C2\u6570");
2319
+ }
2320
+ function debugEnvelopeMeta(result) {
2321
+ if (!isOneBotEnvelope(result)) {
2322
+ return {
2323
+ resultKind: Array.isArray(result) ? "array" : typeof result
2324
+ };
2325
+ }
2326
+ return {
2327
+ resultKind: "envelope",
2328
+ status: previewText(result.status),
2329
+ retcode: typeof result.retcode === "number" ? result.retcode : void 0,
2330
+ dataKind: getDataKind(result.data),
2331
+ dataLength: Array.isArray(result.data) ? result.data.length : void 0,
2332
+ errorMessage: previewText(result.message || result.wording)
2333
+ };
2334
+ }
2335
+ function parseShutListResult(result) {
2336
+ if (Array.isArray(result)) {
2337
+ return { list: result, shouldFallback: false };
2338
+ }
2339
+ if (!isOneBotEnvelope(result)) {
2340
+ return { list: [], shouldFallback: false };
2341
+ }
2342
+ const status = String(result.status || "").toLowerCase();
2343
+ const hasRetcode = typeof result.retcode === "number";
2344
+ const failedByRetcode = hasRetcode && result.retcode !== 0;
2345
+ const failedByStatus = !!status && status !== "ok";
2346
+ if (failedByRetcode || failedByStatus) {
2347
+ if (shouldFallbackByEnvelope(result)) {
2348
+ return { list: [], shouldFallback: true };
2349
+ }
2350
+ const errorText = String(result.message || result.wording || "\u63A5\u53E3\u8C03\u7528\u5931\u8D25\u3002");
2351
+ throw new Error(errorText);
2352
+ }
2353
+ if (Array.isArray(result.data)) {
2354
+ return {
2355
+ list: result.data,
2356
+ shouldFallback: false
2357
+ };
2358
+ }
2359
+ if (result.data == null) {
2360
+ return { list: [], shouldFallback: false };
2361
+ }
2362
+ return { list: [], shouldFallback: false };
2363
+ }
2364
+ async function callGroupShutList(internal, groupId, log, preferredProtocol) {
2365
+ debug(log, "groupShutList: request start", {
2366
+ stage: "request.start",
2367
+ preferredProtocol,
2368
+ groupId,
2369
+ groupIdType: typeof groupId
2370
+ });
2371
+ const { value: result, elapsedMs } = await withTiming(
2372
+ withTimeout(
2373
+ callOneBotAPI(
2374
+ internal,
2375
+ "get_group_shut_list",
2376
+ { group_id: groupId },
2377
+ ["getGroupShutList"]
2378
+ ),
2379
+ GROUP_SHUT_LIST_TIMEOUT_MS
2380
+ )
2381
+ );
2382
+ debug(log, "groupShutList: request resolved", {
2383
+ stage: "request.resolved",
2384
+ preferredProtocol,
2385
+ groupId,
2386
+ groupIdType: typeof groupId,
2387
+ elapsedMs,
2388
+ ...debugEnvelopeMeta(result)
2389
+ });
2390
+ const parsed = parseShutListResult(result);
2391
+ debug(log, "groupShutList: response parsed", {
2392
+ stage: "response.parsed",
2393
+ preferredProtocol,
2394
+ groupId,
2395
+ groupIdType: typeof groupId,
2396
+ elapsedMs,
2397
+ shouldFallback: parsed.shouldFallback,
2398
+ dataLength: parsed.list.length
2399
+ });
2400
+ return parsed;
2401
+ }
2402
+ async function fetchGroupShutList(session, config, log) {
2039
2403
  const { error, internal } = ensureOneBotSession(session);
2040
2404
  if (error || !internal)
2041
2405
  throw new Error(error || "\u7F3A\u5C11 OneBot internal \u63A5\u53E3\u3002");
2042
2406
  const preferredProtocol = resolveOneBotProtocol(config);
2043
2407
  const preferredGroupId = preferredProtocol === "llbot" ? String(session.guildId) : Number(session.guildId);
2044
2408
  const fallbackGroupId = preferredProtocol === "llbot" ? Number(session.guildId) : String(session.guildId);
2409
+ const canFallback = preferredGroupId !== fallbackGroupId;
2410
+ debug(log, "groupShutList: fetch start", {
2411
+ stage: "fetch.start",
2412
+ preferredProtocol,
2413
+ canFallback,
2414
+ groupId: preferredGroupId,
2415
+ groupIdType: typeof preferredGroupId
2416
+ });
2045
2417
  try {
2046
- const result = await callOneBotAPI(
2418
+ const primary = await callGroupShutList(
2047
2419
  internal,
2048
- "get_group_shut_list",
2049
- { group_id: preferredGroupId },
2050
- ["getGroupShutList"]
2420
+ preferredGroupId,
2421
+ log,
2422
+ preferredProtocol
2051
2423
  );
2052
- return extractList(result);
2424
+ if (!canFallback || !primary.shouldFallback) return primary.list;
2425
+ debug(log, "groupShutList: trigger fallback by envelope", {
2426
+ stage: "fallback.envelope",
2427
+ preferredProtocol,
2428
+ canFallback,
2429
+ groupId: fallbackGroupId,
2430
+ groupIdType: typeof fallbackGroupId,
2431
+ shouldFallback: primary.shouldFallback
2432
+ });
2433
+ const fallback = await callGroupShutList(
2434
+ internal,
2435
+ fallbackGroupId,
2436
+ log,
2437
+ preferredProtocol
2438
+ );
2439
+ return fallback.list;
2053
2440
  } catch (error2) {
2054
- if (preferredGroupId === fallbackGroupId) throw error2;
2055
- const result = await callOneBotAPI(
2441
+ const fallbackByError = shouldFallbackByError(error2);
2442
+ debug(log, "groupShutList: request failed", {
2443
+ stage: "request.error",
2444
+ preferredProtocol,
2445
+ canFallback,
2446
+ errorMessage: debugErrorMessage(error2),
2447
+ shouldFallback: fallbackByError
2448
+ });
2449
+ if (!canFallback || !fallbackByError) throw error2;
2450
+ debug(log, "groupShutList: trigger fallback by error", {
2451
+ stage: "fallback.error",
2452
+ preferredProtocol,
2453
+ groupId: fallbackGroupId,
2454
+ groupIdType: typeof fallbackGroupId,
2455
+ errorMessage: debugErrorMessage(error2)
2456
+ });
2457
+ const fallback = await callGroupShutList(
2056
2458
  internal,
2057
- "get_group_shut_list",
2058
- { group_id: fallbackGroupId },
2059
- ["getGroupShutList"]
2459
+ fallbackGroupId,
2460
+ log,
2461
+ preferredProtocol
2060
2462
  );
2061
- return extractList(result);
2463
+ return fallback.list;
2062
2464
  }
2063
2465
  }
2466
+ function readCache(cache, guildId, now) {
2467
+ const entry = cache.get(guildId);
2468
+ if (!entry) return null;
2469
+ if (entry.expiresAt <= now) {
2470
+ cache.delete(guildId);
2471
+ return null;
2472
+ }
2473
+ return entry.value;
2474
+ }
2475
+ function writeCache(cache, guildId, value, now) {
2476
+ cache.set(guildId, {
2477
+ value,
2478
+ expiresAt: now + GROUP_SHUT_LIST_CACHE_TTL_MS
2479
+ });
2480
+ }
2064
2481
  function renderShutList(items) {
2065
2482
  if (items.length === 0) return "\u5F53\u524D\u7FA4\u6682\u65E0\u7981\u8A00\u6210\u5458\u3002";
2066
2483
  return items.map(
@@ -2069,18 +2486,47 @@ function renderShutList(items) {
2069
2486
  }
2070
2487
  function createGroupShutListProvider(deps) {
2071
2488
  const { config, log } = deps;
2489
+ const cache = /* @__PURE__ */ new Map();
2072
2490
  return async (_args, _variables, configurable) => {
2073
2491
  const session = configurable?.session;
2074
2492
  if (!session) return "\u6682\u65E0\u7FA4\u7981\u8A00\u5217\u8868\u3002";
2075
2493
  if (!session.guildId) return "";
2076
2494
  if (session.platform !== "onebot")
2077
2495
  return "\u5F53\u524D\u5E73\u53F0\u6682\u4E0D\u652F\u6301\u67E5\u8BE2\u7FA4\u7981\u8A00\u5217\u8868\u3002";
2496
+ const guildId = String(session.guildId);
2497
+ const cached = readCache(cache, guildId, Date.now());
2498
+ if (cached !== null) {
2499
+ debug(log, "groupShutList: cache hit", {
2500
+ stage: "cache.hit",
2501
+ groupId: guildId,
2502
+ groupIdType: typeof guildId
2503
+ });
2504
+ return cached;
2505
+ }
2506
+ debug(log, "groupShutList: cache miss", {
2507
+ stage: "cache.miss",
2508
+ groupId: guildId,
2509
+ groupIdType: typeof guildId
2510
+ });
2078
2511
  try {
2079
- const list = await fetchGroupShutList(session, config);
2512
+ const list = await fetchGroupShutList(session, config, log);
2080
2513
  const normalized = list.map(normalizeShutItem).filter(Boolean);
2081
- return renderShutList(normalized);
2514
+ const result = renderShutList(normalized);
2515
+ writeCache(cache, guildId, result, Date.now());
2516
+ debug(log, "groupShutList: provider success", {
2517
+ stage: "provider.success",
2518
+ groupId: guildId,
2519
+ dataLength: list.length
2520
+ });
2521
+ return result;
2082
2522
  } catch (error) {
2523
+ debug(log, "groupShutList: provider failed", {
2524
+ stage: "provider.error",
2525
+ groupId: guildId,
2526
+ errorMessage: debugErrorMessage(error)
2527
+ });
2083
2528
  log?.("debug", "\u7FA4\u7981\u8A00\u5217\u8868\u53D8\u91CF\u89E3\u6790\u5931\u8D25", error);
2529
+ if (isTimeoutError(error)) return "\u5F53\u524D\u7FA4\u6682\u65E0\u7981\u8A00\u6210\u5458\u3002";
2084
2530
  return "\u83B7\u53D6\u7FA4\u7981\u8A00\u5217\u8868\u5931\u8D25\u3002";
2085
2531
  }
2086
2532
  };
@@ -2432,8 +2878,10 @@ function apply(ctx, config) {
2432
2878
  createRandomProvider,
2433
2879
  createSetGroupBanTool,
2434
2880
  createSetGroupCardTool,
2881
+ createSetGroupSpecialTitleTool,
2435
2882
  createSetMsgEmojiTool,
2436
2883
  createSetProfileTool,
2884
+ createSetQQAvatarTool,
2437
2885
  createUserInfoProvider,
2438
2886
  createXmlProcessor,
2439
2887
  ensureOneBotSession,
@@ -2452,5 +2900,7 @@ function apply(ctx, config) {
2452
2900
  sendMsgEmoji,
2453
2901
  sendPoke,
2454
2902
  sendSetGroupCard,
2455
- sendSetProfile
2903
+ sendSetGroupSpecialTitle,
2904
+ sendSetProfile,
2905
+ sendSetQQAvatar
2456
2906
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-toolbox",
3
3
  "description": "为 ChatLuna 提供更多原生工具、XML 工具与变量,仅支持 onebot 平台。",
4
- "version": "0.0.12",
4
+ "version": "0.0.15",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [
@@ -10,10 +10,10 @@
10
10
  "client"
11
11
  ],
12
12
  "license": "MIT",
13
- "homepage": "https://github.com/Sor85/koishi-plugins",
13
+ "homepage": "https://github.com/Sor85/AAAAACAT-chatluna-plugins",
14
14
  "repository": {
15
15
  "type": "git",
16
- "url": "https://github.com/Sor85/koishi-plugins.git"
16
+ "url": "https://github.com/Sor85/AAAAACAT-chatluna-plugins.git"
17
17
  },
18
18
  "keywords": [
19
19
  "chatbot",
@@ -54,7 +54,7 @@
54
54
  "scripts": {
55
55
  "build": "tsup && vite build",
56
56
  "watch": "tsup --watch",
57
- "typecheck": "NODE_OPTIONS=--max-old-space-size=8192 tsc --noEmit",
57
+ "typecheck": "NODE_OPTIONS=--max-old-space-size=8192 tsc -p tsconfig.test.json",
58
58
  "test": "vitest run"
59
59
  }
60
60
  }