mioki 0.12.2 → 0.14.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/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_package = require('./package-BQBq2Zmr.cjs');
1
+ const require_package = require('./package-xEK88jTG.cjs');
2
2
  let node_fs = require("node:fs");
3
3
  node_fs = require_package.__toESM(node_fs);
4
4
  let node_util = require("node:util");
@@ -790,12 +790,20 @@ var config_exports = /* @__PURE__ */ require_package.__exportAll({
790
790
  isInPm2: () => isInPm2,
791
791
  isOwner: () => isOwner,
792
792
  isOwnerOrAdmin: () => isOwnerOrAdmin,
793
+ normalizeNapCatConfig: () => normalizeNapCatConfig,
793
794
  readMiokiConfig: () => readMiokiConfig,
794
795
  readPackageJson: () => readPackageJson,
795
796
  updateBotCWD: () => updateBotCWD,
796
797
  updateBotConfig: () => updateBotConfig,
797
798
  writePackageJson: () => writePackageJson
798
799
  });
800
+ function isSingleNapCatConfig(config) {
801
+ return !Array.isArray(config);
802
+ }
803
+ function normalizeNapCatConfig(config) {
804
+ if (isSingleNapCatConfig(config)) return [config];
805
+ return config;
806
+ }
799
807
  /**
800
808
  * 机器人根目录
801
809
  */
@@ -811,7 +819,10 @@ function readMiokiConfig() {
811
819
  const config = readPackageJson().mioki;
812
820
  if (!config) throw new Error(`无法在 package.json 中找到 mioki 配置,请确认 package.json 文件中是否包含 mioki 字段`);
813
821
  if (!config.napcat) throw new Error(`mioki 配置中缺少 napcat 字段,请补全后重试`);
814
- return readPackageJson().mioki;
822
+ return {
823
+ ...config,
824
+ napcat: normalizeNapCatConfig(config.napcat)
825
+ };
815
826
  }
816
827
  /**
817
828
  * `mioki` 框架相关配置
@@ -1212,9 +1223,58 @@ function addService(name, service, cover = true) {
1212
1223
  function bindBot(bot, func) {
1213
1224
  return (...args) => func(bot, ...args);
1214
1225
  }
1226
+ /**
1227
+ * 消息去重器
1228
+ * 处理多个 bot 在同一个群时,同一消息只处理一次
1229
+ */
1230
+ var MessageDeduplicator = class {
1231
+ processedMessages = /* @__PURE__ */ new Set();
1232
+ maxSize = 1e3;
1233
+ /**
1234
+ * 生成消息唯一键
1235
+ * 对于群消息:使用 group_id:user_id:time
1236
+ * 对于私聊消息:使用 private:user_id:time
1237
+ */
1238
+ getKey(event) {
1239
+ if (event.message_type === "group") return `group:${event.group_id}:${event.user_id}:${event.time}`;
1240
+ else return `private:${event.user_id}:${event.time}`;
1241
+ }
1242
+ isProcessed(event) {
1243
+ return this.processedMessages.has(this.getKey(event));
1244
+ }
1245
+ markProcessed(event) {
1246
+ if (this.processedMessages.size >= this.maxSize) {
1247
+ const first = this.processedMessages.values().next();
1248
+ if (!first.done) this.processedMessages.delete(first.value);
1249
+ }
1250
+ this.processedMessages.add(this.getKey(event));
1251
+ }
1252
+ clear() {
1253
+ this.processedMessages.clear();
1254
+ }
1255
+ };
1256
+ const deduplicator = new MessageDeduplicator();
1215
1257
  const runtimePlugins = /* @__PURE__ */ new Map();
1216
1258
  const buildRemovedActions = (bot) => Object.fromEntries(Object.entries(actions_exports).map(([k, v]) => [k, bindBot(bot, v)]));
1217
1259
  /**
1260
+ * 检查事件是否是群消息事件
1261
+ */
1262
+ function isGroupMessageEvent(event) {
1263
+ return event?.post_type === "message" && event?.message_type === "group";
1264
+ }
1265
+ /**
1266
+ * 检查事件是否是私聊消息事件
1267
+ */
1268
+ function isPrivateMessageEvent(event) {
1269
+ return event?.post_type === "message" && event?.message_type === "private";
1270
+ }
1271
+ /**
1272
+ * 检查事件是否是消息事件
1273
+ */
1274
+ function isMessageEvent(event) {
1275
+ return isGroupMessageEvent(event) || isPrivateMessageEvent(event);
1276
+ }
1277
+ /**
1218
1278
  * 定义一个 Mioki 插件
1219
1279
  * @param plugin Mioki 插件对象
1220
1280
  * @returns Mioki 插件对象
@@ -1236,55 +1296,79 @@ function getAbsPluginDir(defaultDir = "plugins") {
1236
1296
  const cwd = BOT_CWD.value;
1237
1297
  return node_path.default.join(cwd, botConfig.plugins_dir || defaultDir);
1238
1298
  }
1239
- async function enablePlugin(bot, plugin, type = "external") {
1299
+ async function enablePlugin(bots, plugin, type = "external") {
1240
1300
  const typeDesc = type === "builtin" ? "内置" : "用户";
1241
1301
  const pluginName = plugin.name || "null";
1242
1302
  const { name = pluginName, version: version$1 = "null", description = "-", setup = () => {} } = plugin;
1303
+ if (!bots[0]) throw new Error("没有可用的 bot 实例");
1243
1304
  try {
1244
1305
  const start$1 = node_process.hrtime.bigint();
1245
1306
  const clears = /* @__PURE__ */ new Set();
1246
1307
  const userClears = /* @__PURE__ */ new Set();
1247
- const logger$1 = bot.logger.withDefaults({
1248
- tag: `plugin:${name}`,
1249
- args: [name]
1250
- });
1251
- const context = {
1252
- bot,
1253
- segment: bot.segment,
1254
- getCookie: bot.getCookie.bind(bot),
1255
- ...utils_exports,
1256
- ...config_exports,
1257
- ...buildRemovedActions(bot),
1258
- logger: logger$1,
1259
- services,
1260
- clears: userClears,
1261
- addService: (name$1, service, cover) => {
1262
- const remove = addService(name$1, service, cover);
1263
- clears.add(remove);
1264
- return remove;
1265
- },
1266
- handle: (eventName, handler) => {
1267
- logger$1.debug(`Registering event handler for event: ${String(eventName)}`);
1268
- bot.on(eventName, handler);
1269
- const unsubscribe = () => {
1270
- logger$1.debug(`Unregistering event handler for event: ${String(eventName)}`);
1271
- bot.off(eventName, handler);
1272
- };
1273
- clears.add(unsubscribe);
1274
- return unsubscribe;
1275
- },
1276
- cron: (cronExpression, handler) => {
1277
- logger$1.debug(`Scheduling cron job: ${cronExpression}`);
1278
- const job = node_cron.default.schedule(cronExpression, (now) => handler(context, now));
1279
- const clear = () => {
1280
- logger$1.debug(`Stopping cron job: ${cronExpression}`);
1281
- job.stop();
1282
- };
1283
- clears.add(clear);
1284
- return job;
1285
- }
1308
+ const logger$1 = logger.withDefaults({ tag: `plugin:${name}` });
1309
+ const createContext = (bot) => {
1310
+ return {
1311
+ bot,
1312
+ bots,
1313
+ self_id: bot.bot_id,
1314
+ segment: bot.segment,
1315
+ getCookie: bot.getCookie.bind(bot),
1316
+ ...utils_exports,
1317
+ ...config_exports,
1318
+ ...buildRemovedActions(bot),
1319
+ logger: logger$1,
1320
+ services,
1321
+ clears: userClears,
1322
+ deduplicator,
1323
+ addService: (name$1, service, cover) => {
1324
+ const remove = addService(name$1, service, cover);
1325
+ clears.add(remove);
1326
+ return remove;
1327
+ },
1328
+ handle: (eventName, handler) => {
1329
+ logger$1.debug(`Registering event handler for event: ${String(eventName)}`);
1330
+ const unsubscribes = [];
1331
+ for (const bot$1 of bots) {
1332
+ const wrappedHandler = (event) => {
1333
+ if (isMessageEvent(event)) {
1334
+ const messageEvent = event;
1335
+ if (isPrivateMessageEvent(messageEvent)) {
1336
+ if (messageEvent.self_id !== bot$1.bot_id) return;
1337
+ }
1338
+ if (isGroupMessageEvent(messageEvent)) {
1339
+ if (deduplicator.isProcessed(messageEvent)) return;
1340
+ deduplicator.markProcessed(messageEvent);
1341
+ }
1342
+ }
1343
+ handler(event);
1344
+ };
1345
+ bot$1.on(eventName, wrappedHandler);
1346
+ const unsubscribe = () => {
1347
+ logger$1.debug(`Unregistering event handler for event: ${String(eventName)}`);
1348
+ bot$1.off(eventName, wrappedHandler);
1349
+ };
1350
+ unsubscribes.push(unsubscribe);
1351
+ }
1352
+ const clearAll = () => {
1353
+ unsubscribes.forEach((fn) => fn());
1354
+ };
1355
+ clears.add(clearAll);
1356
+ return clearAll;
1357
+ },
1358
+ cron: (cronExpression, handler) => {
1359
+ logger$1.debug(`Scheduling cron job: ${cronExpression}`);
1360
+ const job = node_cron.default.schedule(cronExpression, (now) => handler(createContext(bot), now));
1361
+ const clear = () => {
1362
+ logger$1.debug(`Stopping cron job: ${cronExpression}`);
1363
+ job.stop();
1364
+ };
1365
+ clears.add(clear);
1366
+ return job;
1367
+ }
1368
+ };
1286
1369
  };
1287
- clears.add(await setup(context) || (() => {}));
1370
+ const mainContext = createContext(bots[0]);
1371
+ clears.add(await setup(mainContext) || (() => {}));
1288
1372
  runtimePlugins.set(name, {
1289
1373
  name,
1290
1374
  type,
@@ -1303,7 +1387,7 @@ async function enablePlugin(bot, plugin, type = "external") {
1303
1387
  });
1304
1388
  const end = node_process.hrtime.bigint();
1305
1389
  const time = Math.round(Number(end - start$1)) / 1e6;
1306
- bot.logger.info(`- 启用插件 ${consola_utils.colors.yellow(`[${typeDesc}]`)} ${consola_utils.colors.yellow(`${name}@${version$1}`)} => 耗时 ${consola_utils.colors.green(time.toFixed(2))} 毫秒`);
1390
+ logger$1.info(`- 启用插件 ${consola_utils.colors.yellow(`[${typeDesc}]`)} ${consola_utils.colors.yellow(`${name}@${version$1}`)} => 耗时 ${consola_utils.colors.green(time.toFixed(2))} 毫秒`);
1307
1391
  } catch (e) {
1308
1392
  throw new Error(`启用插件 ${consola_utils.colors.yellow(`[${typeDesc}]`)} ${consola_utils.colors.yellow(`${name}@${version$1}`)} 失败: ${e?.message}`);
1309
1393
  }
@@ -1329,18 +1413,12 @@ const ArchMap = {
1329
1413
  arm64: "arm64",
1330
1414
  x64: "x64"
1331
1415
  };
1332
- async function getMiokiStatus(bot) {
1416
+ async function getMiokiStatus(bots) {
1333
1417
  const osType = node_os.default.type();
1334
1418
  const osArch = node_os.default.arch();
1335
1419
  const isInUnix = ["Linux", "Darwin"].includes(osType);
1336
1420
  const arch = ArchMap[osArch] || osArch;
1337
- const [osInfo, localPlugins, versionInfo, friendList, groupList] = await Promise.all([
1338
- systeminformation.default.osInfo(),
1339
- findLocalPlugins(),
1340
- bot.getVersionInfo(),
1341
- bot.getFriendList(),
1342
- bot.getGroupList()
1343
- ]);
1421
+ const [osInfo, localPlugins] = await Promise.all([systeminformation.default.osInfo(), findLocalPlugins()]);
1344
1422
  const pluginCount = localPlugins.length + BUILTIN_PLUGINS.length;
1345
1423
  const system = isInUnix ? {
1346
1424
  name: osInfo.distro,
@@ -1354,27 +1432,58 @@ async function getMiokiStatus(bot) {
1354
1432
  const rssMem = process.memoryUsage.rss();
1355
1433
  const nodeVersion = process.versions.node;
1356
1434
  const cpu = getCpuInfo();
1357
- return {
1358
- bot: {
1359
- uin: bot.uin,
1435
+ const botStatuses = [];
1436
+ let totalSend = 0;
1437
+ let totalReceive = 0;
1438
+ let mainVersionInfo = {
1439
+ app_version: "unknown",
1440
+ protocol_version: "unknown"
1441
+ };
1442
+ for (const bot of bots) try {
1443
+ const [versionInfo, friendList, groupList] = await Promise.all([
1444
+ bot.getVersionInfo(),
1445
+ bot.getFriendList(),
1446
+ bot.getGroupList()
1447
+ ]);
1448
+ mainVersionInfo = versionInfo;
1449
+ botStatuses.push({
1450
+ uin: bot.bot_id,
1360
1451
  nickname: bot.nickname,
1452
+ name: bot.name,
1361
1453
  friends: friendList.length,
1362
- groups: groupList.length
1363
- },
1454
+ groups: groupList.length,
1455
+ send: bot.stat.send.group + bot.stat.send.private,
1456
+ receive: bot.stat.recv.group + bot.stat.recv.private
1457
+ });
1458
+ totalSend += bot.stat.send.group + bot.stat.send.private;
1459
+ totalReceive += bot.stat.recv.group + bot.stat.recv.private;
1460
+ } catch (err) {
1461
+ botStatuses.push({
1462
+ uin: bot.bot_id,
1463
+ nickname: bot.nickname,
1464
+ name: bot.name,
1465
+ friends: 0,
1466
+ groups: 0,
1467
+ send: 0,
1468
+ receive: 0
1469
+ });
1470
+ }
1471
+ return {
1472
+ bots: botStatuses,
1364
1473
  plugins: {
1365
1474
  enabled: runtimePlugins.size,
1366
1475
  total: pluginCount
1367
1476
  },
1368
1477
  stats: {
1369
1478
  uptime: process.uptime() * 1e3,
1370
- send: bot.stat.send.group + bot.stat.send.private,
1371
- receive: bot.stat.recv.group + bot.stat.recv.private
1479
+ send: totalSend,
1480
+ receive: totalReceive
1372
1481
  },
1373
1482
  versions: {
1374
1483
  node: nodeVersion,
1375
1484
  mioki: require_package.version,
1376
- napcat: versionInfo.app_version,
1377
- protocol: versionInfo.protocol_version
1485
+ napcat: mainVersionInfo.app_version,
1486
+ protocol: mainVersionInfo.protocol_version
1378
1487
  },
1379
1488
  system: {
1380
1489
  name: system.name || "N/A",
@@ -1404,16 +1513,16 @@ async function getMiokiStatus(bot) {
1404
1513
  };
1405
1514
  }
1406
1515
  async function formatMiokiStatus(status) {
1407
- const { bot, plugins, stats, system, disk, cpu, memory, versions } = status;
1516
+ const { bots, plugins, stats, system, disk, cpu, memory, versions } = status;
1408
1517
  const diskValid = disk.total > 0 && disk.free >= 0;
1409
1518
  const diskDesc = `${disk.percent}%-${(0, filesize.filesize)(disk.used, { round: 1 })}/${(0, filesize.filesize)(disk.total, { round: 1 })}`;
1410
1519
  return `
1411
1520
  〓 🟢 mioki 状态 〓
1412
- 👤 ${bot.nickname}
1413
- 🆔 ${bot.uin}
1414
- 📋 ${localNum(bot.friends)} 好友 / ${localNum(bot.groups)}
1521
+ ${bots.map((bot, index) => {
1522
+ return `👤 ${bot.name ? `[${bot.name}] ` : ""}${bot.nickname} (${bot.uin})\n 📋 ${localNum(bot.friends)} 好友 / ${localNum(bot.groups)} 群 / 📮 收 ${localNum(bot.receive)} 发 ${localNum(bot.send)}`;
1523
+ }).join("\n")}
1415
1524
  🧩 启用了 ${localNum(plugins.enabled)} 个插件,共 ${localNum(plugins.total)} 个
1416
- 📮 收 ${localNum(stats.receive)} 条,发 ${localNum(stats.send)} 条
1525
+ 📮 总计: 收 ${localNum(stats.receive)} 条,发 ${localNum(stats.send)} 条
1417
1526
  🚀 ${(0, filesize.filesize)(memory.rss.used, { round: 1 })}/${memory.percent}%
1418
1527
  ⏳ 已运行 ${(0, pretty_ms.default)(stats.uptime, {
1419
1528
  hideYear: true,
@@ -1497,7 +1606,7 @@ const core = definePlugin({
1497
1606
  const displayPrefix = prefix.replace(/\\\\/g, "\\");
1498
1607
  const statusAdminOnly = ctx.botConfig.status_permission === "admin-only";
1499
1608
  let statusFormatter = (status) => formatMiokiStatus(status);
1500
- ctx.addService("getMiokiStatus", () => getMiokiStatus(ctx.bot));
1609
+ ctx.addService("getMiokiStatus", () => getMiokiStatus(ctx.bots));
1501
1610
  ctx.addService("formatMiokiStatus", (status) => formatMiokiStatus(status));
1502
1611
  ctx.addService("customFormatMiokiStatus", (formatter) => statusFormatter = formatter);
1503
1612
  ctx.handle("message", (e) => ctx.runWithErrorHandler(async () => {
@@ -1505,7 +1614,7 @@ const core = definePlugin({
1505
1614
  if (!cmdPrefix.test(text$1)) return;
1506
1615
  if (statusAdminOnly && !ctx.hasRight(e)) return;
1507
1616
  if (text$1.replace(cmdPrefix, "") === "状态") {
1508
- const status = await statusFormatter(await getMiokiStatus(ctx.bot));
1617
+ const status = await statusFormatter(await getMiokiStatus(ctx.bots));
1509
1618
  await e.reply(status);
1510
1619
  return;
1511
1620
  }
@@ -1570,10 +1679,10 @@ const core = definePlugin({
1570
1679
  const plugin = await ctx.jiti.import(pluginPath, { default: true });
1571
1680
  if (plugin.name !== target) {
1572
1681
  const tip = `[插件目录名称: ${target}] 和插件代码中设置的 [name: ${plugin.name}] 不一致,可能导致重载异常,请修改后重启。`;
1573
- ctx.bot.logger.warn(tip);
1682
+ ctx.logger.warn(tip);
1574
1683
  ctx.noticeMainOwner(tip);
1575
1684
  }
1576
- await enablePlugin(ctx.bot, plugin);
1685
+ await enablePlugin(ctx.bots, plugin);
1577
1686
  } catch (err) {
1578
1687
  await e.reply(`插件 ${target} 启用失败:${err?.message || "未知错误"}`, true);
1579
1688
  return;
@@ -1599,7 +1708,7 @@ const core = definePlugin({
1599
1708
  break;
1600
1709
  }
1601
1710
  await ctx.updateBotConfig((c) => c.plugins = ctx.botConfig.plugins.filter((name) => name !== target));
1602
- ctx.bot.logger.info(`禁用插件 => ${target}`);
1711
+ ctx.logger.info(`禁用插件 => ${target}`);
1603
1712
  await e.reply(`插件 ${target} 已禁用`, true);
1604
1713
  break;
1605
1714
  }
@@ -1621,10 +1730,10 @@ const core = definePlugin({
1621
1730
  const importedPlugin = await ctx.jiti.import(pluginPath, { default: true });
1622
1731
  if (importedPlugin.name !== target) {
1623
1732
  const tip = `插件目录名称: ${target} 和插件代码中设置的 name: ${importedPlugin.name} 不一致,可能导致重载异常,请修改后重启。`;
1624
- ctx.bot.logger.warn(tip);
1733
+ ctx.logger.warn(tip);
1625
1734
  ctx.noticeMainOwner(tip);
1626
1735
  }
1627
- await enablePlugin(ctx.bot, importedPlugin);
1736
+ await enablePlugin(ctx.bots, importedPlugin);
1628
1737
  } catch (err) {
1629
1738
  await e.reply(err?.message, true);
1630
1739
  await ctx.updateBotConfig((c) => c.plugins = c.plugins.filter((name) => name !== target));
@@ -1737,7 +1846,7 @@ const core = definePlugin({
1737
1846
  break;
1738
1847
  case "退出":
1739
1848
  await e.reply("またね~", true);
1740
- ctx.bot.logger.info("接收到退出指令,即将退出... 如需自动重启,请使用 pm2 部署。");
1849
+ ctx.logger.info("接收到退出指令,即将退出... 如需自动重启,请使用 pm2 部署。");
1741
1850
  process.exit(0);
1742
1851
  }
1743
1852
  }, e));
@@ -1751,6 +1860,120 @@ const BUILTIN_PLUGINS = [core_default];
1751
1860
 
1752
1861
  //#endregion
1753
1862
  //#region src/start.ts
1863
+ const connectedBots = /* @__PURE__ */ new Map();
1864
+ async function connectBot(config, index) {
1865
+ const { protocol = "ws", port = 3001, host = "localhost", token = "", name } = config;
1866
+ const botName = name || `Bot${index + 1}`;
1867
+ const wsUrl = consola_utils.colors.green(`${protocol}://${host}:${port}${token ? "?access_token=***" : ""}`);
1868
+ logger.info(`>>> 正在连接 ${consola_utils.colors.cyan(botName)}: ${wsUrl}`);
1869
+ const napcat = new napcat_sdk.NapCat({
1870
+ token,
1871
+ protocol,
1872
+ host,
1873
+ port,
1874
+ logger
1875
+ });
1876
+ return new Promise((resolve) => {
1877
+ napcat.on("ws.close", () => {
1878
+ logger.warn(`${consola_utils.colors.yellow(botName)} WS 连接已关闭`);
1879
+ });
1880
+ napcat.on("ws.error", (err) => {
1881
+ logger.error(`${consola_utils.colors.red(botName)} WS 连接错误: ${err}`);
1882
+ });
1883
+ napcat.once("napcat.connected", ({ user_id, nickname, app_name, app_version }) => {
1884
+ logger.info(`已连接到 ${consola_utils.colors.cyan(botName)}: ${consola_utils.colors.green(`${app_name}-v${app_version} ${nickname}(${user_id})`)}`);
1885
+ if (connectedBots.has(user_id)) {
1886
+ const existingBot = connectedBots.get(user_id);
1887
+ if (existingBot.name) logger.warn(`${consola_utils.colors.yellow(botName)} (${user_id}) 与 ${consola_utils.colors.yellow(existingBot.name)} (${user_id}) QQ 号重复,将跳过`);
1888
+ napcat.close();
1889
+ resolve(null);
1890
+ return;
1891
+ }
1892
+ const extendedNapCat = napcat;
1893
+ extendedNapCat.bot_id = user_id;
1894
+ extendedNapCat.app_name = app_name;
1895
+ extendedNapCat.app_version = app_version;
1896
+ extendedNapCat.name = botName;
1897
+ resolve(extendedNapCat);
1898
+ });
1899
+ napcat.run().catch((err) => {
1900
+ logger.error(`${consola_utils.colors.red(botName)} 连接失败: ${err.message}`);
1901
+ resolve(null);
1902
+ });
1903
+ });
1904
+ }
1905
+ async function setupPlugins(napcat, bots) {
1906
+ const plugin_dir = getAbsPluginDir();
1907
+ const mainBot = napcat;
1908
+ ensurePluginDir();
1909
+ const plugins = botConfig.plugins.map((p) => ({
1910
+ dirName: p,
1911
+ absPath: node_path.default.resolve(plugin_dir, p)
1912
+ })).filter((p) => {
1913
+ if (!node_fs.default.existsSync(p.absPath)) {
1914
+ mainBot.logger.warn(`插件 ${consola_utils.colors.red(p.dirName)} 不存在,已忽略`);
1915
+ return false;
1916
+ }
1917
+ return true;
1918
+ });
1919
+ const failedImportPlugins = [];
1920
+ const promises = plugins.map(async ({ absPath, dirName }) => {
1921
+ try {
1922
+ const plugin = await jiti$1.import(absPath, { default: true });
1923
+ if (plugin.name !== dirName) {
1924
+ const tip = `插件目录名 [${consola_utils.colors.yellow(dirName)}] 和插件声明的 name [${consola_utils.colors.yellow(plugin.name)}] 不一致,可能导致重载异常,请修改一致后重启。`;
1925
+ mainBot.logger.warn(tip);
1926
+ noticeMainOwner(mainBot, tip);
1927
+ }
1928
+ return plugin;
1929
+ } catch (e) {
1930
+ const err = stringifyError(e);
1931
+ failedImportPlugins.push([dirName, err]);
1932
+ return null;
1933
+ }
1934
+ });
1935
+ const start$1 = node_process.hrtime.bigint();
1936
+ const sortedUserPlugins = (await Promise.all(promises)).filter(Boolean).toSorted((prev, next) => (prev.priority ?? 100) - (next.priority ?? 100));
1937
+ if (failedImportPlugins.length) {
1938
+ const tip = `${consola_utils.colors.red(failedImportPlugins.length)} 个插件加载失败: \n\n${failedImportPlugins.map(([dirName, err]) => `${dirName}: ${err}`).join("\n\n")}`;
1939
+ mainBot.logger.warn(tip);
1940
+ noticeMainOwner(mainBot, tip);
1941
+ }
1942
+ const pluginGroups = /* @__PURE__ */ new Map();
1943
+ for (const plugin of sortedUserPlugins) {
1944
+ const priority = plugin.priority ?? 100;
1945
+ if (!pluginGroups.has(priority)) pluginGroups.set(priority, []);
1946
+ pluginGroups.get(priority).push(plugin);
1947
+ }
1948
+ const sortedGroups = Array.from(pluginGroups.entries()).toSorted(([a], [b]) => a - b);
1949
+ const failedEnablePlugins = [];
1950
+ try {
1951
+ mainBot.logger.info(`>>> 加载内置插件: ${BUILTIN_PLUGINS.map((p) => consola_utils.colors.cyan(p.name)).join(", ")}`);
1952
+ await Promise.all(BUILTIN_PLUGINS.map((p) => enablePlugin(bots, p, "builtin")));
1953
+ mainBot.logger.info(`>>> 加载用户插件: ${sortedGroups.map(([priority, plugins$1]) => `优先级 ${consola_utils.colors.yellow(priority)} (${plugins$1.map((p) => consola_utils.colors.cyan(p.name)).join(", ")})`).join(",")}`);
1954
+ for (const [_, plugins$1] of sortedGroups) await Promise.all(plugins$1.map(async (p) => {
1955
+ try {
1956
+ await enablePlugin(bots, p, "external");
1957
+ } catch (e) {
1958
+ failedEnablePlugins.push([p.name, stringifyError(e)]);
1959
+ }
1960
+ }));
1961
+ } catch (e) {
1962
+ mainBot.logger.error(e?.message);
1963
+ await noticeMainOwner(mainBot, e?.message).catch(() => {
1964
+ mainBot.logger.error("发送插件启用失败通知失败");
1965
+ });
1966
+ }
1967
+ const end = node_process.hrtime.bigint();
1968
+ const costTime = Math.round(Number(end - start$1)) / 1e6;
1969
+ const failedCount = failedImportPlugins.length + failedEnablePlugins.length;
1970
+ const failedInfo = failedCount > 0 ? `${consola_utils.colors.red(failedCount)} 个失败 (导入 ${consola_utils.colors.red(failedImportPlugins.length)},启用 ${consola_utils.colors.red(failedEnablePlugins.length)})` : "";
1971
+ mainBot.logger.info(`成功加载了 ${consola_utils.colors.green(runtimePlugins.size)} 个插件,${failedInfo ? failedInfo : ""}总耗时 ${consola_utils.colors.green(costTime.toFixed(2))} 毫秒`);
1972
+ mainBot.logger.info(consola_utils.colors.green(`mioki v${require_package.version} 启动完成,向机器人发送「${consola_utils.colors.magentaBright(`${botConfig.prefix}帮助`)}」查看消息指令`));
1973
+ if (botConfig.online_push) await noticeMainOwner(mainBot, `✅ mioki v${require_package.version} 已就绪`).catch((err) => {
1974
+ mainBot.logger.error(`发送就绪通知失败: ${stringifyError(err)}`);
1975
+ });
1976
+ }
1754
1977
  async function start(options = {}) {
1755
1978
  const { cwd = process.cwd() } = options;
1756
1979
  if (cwd !== BOT_CWD.value) updateBotCWD(node_path.default.resolve(cwd));
@@ -1769,117 +1992,67 @@ async function start(options = {}) {
1769
1992
  logger.info(`${consola_utils.colors.dim("插件目录: ")}${consola_utils.colors.blue(plugin_dir)}`);
1770
1993
  logger.info(`${consola_utils.colors.dim("配置文件: ")}${consola_utils.colors.blue(`${BOT_CWD.value}/package.json`)}`);
1771
1994
  logger.info(consola_utils.colors.dim("=".repeat(40)));
1772
- const { protocol = "ws", port = 3001, host = "localhost", token = "" } = botConfig.napcat || {};
1773
- const wsUrl = consola_utils.colors.green(`${protocol}://${host}:${port}${token ? "?access_token=***" : ""}`);
1774
- logger.info(`>>> 正在连接 NapCat 实例: ${wsUrl}`);
1775
- const napcat = new napcat_sdk.NapCat({
1776
- token,
1777
- protocol,
1778
- host,
1779
- port,
1780
- logger
1781
- });
1782
- napcat.on("ws.close", () => {
1783
- logger.error("WS 连接失败,请确保 token 配置正确且 NapCat 实例正常运行");
1995
+ const napcatConfigs = botConfig.napcat;
1996
+ if (napcatConfigs.length === 0) {
1997
+ logger.warn("未配置任何 NapCat 实例,框架将以无实例模式启动");
1998
+ logger.info(consola_utils.colors.green(`mioki v${require_package.version} 启动完成,向机器人发送「${consola_utils.colors.magentaBright(`${botConfig.prefix}帮助`)}」查看消息指令`));
1999
+ return;
2000
+ }
2001
+ const seenEndpoints = /* @__PURE__ */ new Set();
2002
+ const duplicateConfigs = [];
2003
+ for (const config of napcatConfigs) {
2004
+ const { protocol = "ws", host = "localhost", port = 3001 } = config;
2005
+ const endpoint = `${protocol}://${host}:${port}`;
2006
+ if (seenEndpoints.has(endpoint)) duplicateConfigs.push(`${config.name || "未命名"} (${endpoint})`);
2007
+ else seenEndpoints.add(endpoint);
2008
+ }
2009
+ if (duplicateConfigs.length > 0) {
2010
+ logger.error(`检测到重复的 NapCat 实例配置:`);
2011
+ duplicateConfigs.forEach((dup) => logger.error(` - ${dup}`));
2012
+ logger.error("请检查配置文件,确保每个实例的 host:port 组合唯一");
1784
2013
  process.exit(1);
1785
- });
1786
- napcat.on("napcat.connected", async ({ user_id, nickname, app_name, app_version }) => {
1787
- logger.info(`已连接到 NapCat 实例: ${consola_utils.colors.green(`${app_name}-v${app_version} ${nickname}(${user_id})`)}`);
1788
- process.title = `mioki v${require_package.version} ${app_name}-v${app_version}-${user_id}`;
1789
- let lastNoticeTime = 0;
2014
+ }
2015
+ logger.info(consola_utils.colors.dim("=".repeat(40)));
2016
+ logger.info(`>>> 正在连接 ${napcatConfigs.length} 个 NapCat 实例...`);
2017
+ const bots = (await Promise.all(napcatConfigs.map((config, index) => connectBot(config, index)))).filter((b) => b !== null);
2018
+ if (bots.length === 0) {
2019
+ logger.error("所有 NapCat 实例连接失败,框架无法启动");
2020
+ process.exit(1);
2021
+ }
2022
+ for (const bot of bots) connectedBots.set(bot.bot_id, bot);
2023
+ if (bots.length < napcatConfigs.length) logger.warn(`${consola_utils.colors.yellow(napcatConfigs.length - bots.length)} 个 NapCat 实例连接失败`);
2024
+ const botNames = bots.map((b) => `${b.name}(${b.bot_id})`).join(", ");
2025
+ logger.info(consola_utils.colors.green(`成功连接 ${bots.length} 个实例: ${botNames}`));
2026
+ logger.info(consola_utils.colors.dim("=".repeat(40)));
2027
+ const mainBot = bots[0];
2028
+ process.title = `mioki v${require_package.version} ${bots.map((b) => `${b.bot_id}`).join(", ")}`;
2029
+ let lastNoticeTime = 0;
2030
+ for (const bot of bots) {
1790
2031
  process.on("uncaughtException", async (err) => {
1791
2032
  const msg = stringifyError(err);
1792
- napcat.logger.error(`uncaughtException, 出错了: ${msg}`);
2033
+ bot.logger.error(`uncaughtException, 出错了: ${msg}`);
1793
2034
  if (botConfig.error_push) {
1794
2035
  if (Date.now() - lastNoticeTime < 1e3) return;
1795
2036
  lastNoticeTime = Date.now();
1796
- await noticeMainOwner(napcat, `mioki 发生未捕获异常:\n\n${msg}`).catch(() => {
1797
- napcat.logger.error("发送未捕获异常通知失败");
2037
+ await noticeMainOwner(mainBot, `mioki 发生未捕获异常:\n\n${msg}`).catch(() => {
2038
+ mainBot.logger.error("发送未捕获异常通知失败");
1798
2039
  });
1799
2040
  }
1800
2041
  });
1801
2042
  process.on("unhandledRejection", async (err) => {
1802
2043
  const msg = stringifyError(err);
1803
- napcat.logger.error(`unhandledRejection, 出错了: ${msg}`);
2044
+ bot.logger.error(`unhandledRejection, 出错了: ${msg}`);
1804
2045
  if (botConfig.error_push) {
1805
2046
  if (Date.now() - lastNoticeTime < 1e3) return;
1806
2047
  lastNoticeTime = Date.now();
1807
2048
  const date = (/* @__PURE__ */ new Date()).toLocaleString();
1808
- await noticeMainOwner(napcat, `【${date}】\n\nmioki 发生未处理异常:\n\n${msg}`).catch(() => {
1809
- napcat.logger.error("发送未处理异常通知失败");
2049
+ await noticeMainOwner(mainBot, `【${date}】\n\nmioki 发生未处理异常:\n\n${msg}`).catch(() => {
2050
+ mainBot.logger.error("发送未处理异常通知失败");
1810
2051
  });
1811
2052
  }
1812
2053
  });
1813
- ensurePluginDir();
1814
- const plugins = botConfig.plugins.map((p) => ({
1815
- dirName: p,
1816
- absPath: node_path.default.resolve(plugin_dir, p)
1817
- })).filter((p) => {
1818
- if (!node_fs.default.existsSync(p.absPath)) {
1819
- napcat.logger.warn(`插件 ${consola_utils.colors.red(p.dirName)} 不存在,已忽略`);
1820
- return false;
1821
- }
1822
- return true;
1823
- });
1824
- const failedImportPlugins = [];
1825
- const promises = plugins.map(async ({ absPath, dirName }) => {
1826
- try {
1827
- const plugin = await jiti$1.import(absPath, { default: true });
1828
- if (plugin.name !== dirName) {
1829
- const tip = `插件目录名 [${consola_utils.colors.yellow(dirName)}] 和插件声明的 name [${consola_utils.colors.yellow(plugin.name)}] 不一致,可能导致重载异常,请修改一致后重启。`;
1830
- napcat.logger.warn(tip);
1831
- noticeMainOwner(napcat, tip);
1832
- }
1833
- return plugin;
1834
- } catch (e) {
1835
- const err = stringifyError(e);
1836
- failedImportPlugins.push([dirName, err]);
1837
- return null;
1838
- }
1839
- });
1840
- const start$1 = node_process.hrtime.bigint();
1841
- const sortedUserPlugins = (await Promise.all(promises)).filter(Boolean).toSorted((prev, next) => (prev.priority ?? 100) - (next.priority ?? 100));
1842
- if (failedImportPlugins.length) {
1843
- const tip = `${consola_utils.colors.red(failedImportPlugins.length)} 个插件加载失败: \n\n${failedImportPlugins.map(([dirName, err]) => `${dirName}: ${err}`).join("\n\n")}`;
1844
- napcat.logger.warn(tip);
1845
- noticeMainOwner(napcat, tip);
1846
- }
1847
- const pluginGroups = /* @__PURE__ */ new Map();
1848
- for (const plugin of sortedUserPlugins) {
1849
- const priority = plugin.priority ?? 100;
1850
- if (!pluginGroups.has(priority)) pluginGroups.set(priority, []);
1851
- pluginGroups.get(priority).push(plugin);
1852
- }
1853
- const sortedGroups = Array.from(pluginGroups.entries()).toSorted(([a], [b]) => a - b);
1854
- const failedEnablePlugins = [];
1855
- try {
1856
- napcat.logger.info(`>>> 加载内置插件: ${BUILTIN_PLUGINS.map((p) => consola_utils.colors.cyan(p.name)).join(", ")}`);
1857
- await Promise.all(BUILTIN_PLUGINS.map((p) => enablePlugin(napcat, p, "builtin")));
1858
- napcat.logger.info(`>>> 加载用户插件: ${sortedGroups.map(([priority, plugins$1]) => `优先级 ${consola_utils.colors.yellow(priority)} (${plugins$1.map((p) => consola_utils.colors.cyan(p.name)).join(", ")})`).join(",")}`);
1859
- for (const [_, plugins$1] of sortedGroups) await Promise.all(plugins$1.map(async (p) => {
1860
- try {
1861
- await enablePlugin(napcat, p, "external");
1862
- } catch (e) {
1863
- failedEnablePlugins.push([p.name, stringifyError(e)]);
1864
- }
1865
- }));
1866
- } catch (e) {
1867
- napcat.logger.error(e?.message);
1868
- await noticeMainOwner(napcat, e?.message).catch(() => {
1869
- napcat.logger.error("发送插件启用失败通知失败");
1870
- });
1871
- }
1872
- const end = node_process.hrtime.bigint();
1873
- const costTime = Math.round(Number(end - start$1)) / 1e6;
1874
- const failedCount = failedImportPlugins.length + failedEnablePlugins.length;
1875
- const failedInfo = failedCount > 0 ? `${consola_utils.colors.red(failedCount)} 个失败 (导入 ${consola_utils.colors.red(failedImportPlugins.length)},启用 ${consola_utils.colors.red(failedEnablePlugins.length)})` : "";
1876
- napcat.logger.info(`成功加载了 ${consola_utils.colors.green(runtimePlugins.size)} 个插件,${failedInfo ? failedInfo : ""}总耗时 ${consola_utils.colors.green(costTime.toFixed(2))} 毫秒`);
1877
- napcat.logger.info(consola_utils.colors.green(`mioki v${require_package.version} 启动完成,向机器人发送「${consola_utils.colors.magentaBright(`${botConfig.prefix}帮助`)}」查看消息指令`));
1878
- if (botConfig.online_push) await noticeMainOwner(napcat, `✅ mioki v${require_package.version} 已就绪`).catch((err) => {
1879
- napcat.logger.error(`发送就绪通知失败: ${stringifyError(err)}`);
1880
- });
1881
- });
1882
- await napcat.run();
2054
+ }
2055
+ await setupPlugins(mainBot, bots);
1883
2056
  }
1884
2057
 
1885
2058
  //#endregion
@@ -1888,6 +2061,7 @@ exports.BOT_CWD = BOT_CWD;
1888
2061
  exports.BUILTIN_PLUGINS = BUILTIN_PLUGINS;
1889
2062
  exports.CORE_PLUGINS = CORE_PLUGINS;
1890
2063
  exports.ChromeUA = ChromeUA;
2064
+ exports.MessageDeduplicator = MessageDeduplicator;
1891
2065
  exports.START_TIME = START_TIME;
1892
2066
  exports.SystemMap = SystemMap;
1893
2067
  exports.addService = addService;
@@ -1914,6 +2088,7 @@ Object.defineProperty(exports, 'colors', {
1914
2088
  return consola_utils.colors;
1915
2089
  }
1916
2090
  });
2091
+ exports.connectedBots = connectedBots;
1917
2092
  exports.createCmd = createCmd;
1918
2093
  exports.createDB = createDB;
1919
2094
  exports.createForwardMsg = createForwardMsg;
@@ -1930,6 +2105,7 @@ Object.defineProperty(exports, 'dedent', {
1930
2105
  return dedent.default;
1931
2106
  }
1932
2107
  });
2108
+ exports.deduplicator = deduplicator;
1933
2109
  exports.definePlugin = definePlugin;
1934
2110
  exports.enablePlugin = enablePlugin;
1935
2111
  exports.ensureBuffer = ensureBuffer;
@@ -2000,6 +2176,7 @@ Object.defineProperty(exports, 'mri', {
2000
2176
  }
2001
2177
  });
2002
2178
  exports.noNullish = noNullish;
2179
+ exports.normalizeNapCatConfig = normalizeNapCatConfig;
2003
2180
  exports.noticeAdmins = noticeAdmins;
2004
2181
  exports.noticeFriends = noticeFriends;
2005
2182
  exports.noticeGroups = noticeGroups;