mioku 1.0.7 → 1.0.9

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { buildSpawnPlan, commandExists, resolveCommand, runCommand, runCommandInherit, version } from "./exec-BxcCq8G9.js";
1
+ import { buildSpawnPlan, commandExists, resolveCommand, runCommand, runCommandInherit, version } from "./exec-C3QTbHLt.js";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs$5 from "node:fs";
4
4
  import * as fs$4 from "node:fs";
@@ -13,7 +13,7 @@ import path, { default as path$1 } from "node:path";
13
13
  import { fileURLToPath, pathToFileURL } from "node:url";
14
14
  import { hrtime } from "node:process";
15
15
  import { box, colorize, colors, colors as colors$1, stripAnsi } from "consola/utils";
16
- import crypto, { randomUUID } from "node:crypto";
16
+ import crypto, { createHash, randomUUID } from "node:crypto";
17
17
  import nodeCron from "node-cron";
18
18
  import mriLib from "mri";
19
19
  import { Low } from "lowdb";
@@ -870,6 +870,8 @@ var AdapterContextImpl = class {
870
870
  #capabilities;
871
871
  #logger;
872
872
  #emit;
873
+ #correlator;
874
+ #commands;
873
875
  #pendingStarts = new Set();
874
876
  constructor(options) {
875
877
  this.#state = options.state;
@@ -879,6 +881,8 @@ var AdapterContextImpl = class {
879
881
  this.#capabilities = options.capabilities;
880
882
  this.#logger = options.logger;
881
883
  this.#emit = options.emit;
884
+ this.#correlator = options.correlator;
885
+ this.#commands = options.commands;
882
886
  }
883
887
  registerBot(bot) {
884
888
  return this.#bots.register(bot);
@@ -942,6 +946,7 @@ var AdapterContextImpl = class {
942
946
  };
943
947
  }
944
948
  async dispatch(event) {
949
+ this.#correlator?.observe(event);
945
950
  if (event.kind === "message") {
946
951
  const senderId = event.user_id;
947
952
  if (senderId != null) {
@@ -952,6 +957,12 @@ var AdapterContextImpl = class {
952
957
  }
953
958
  }
954
959
  }
960
+ if (event.kind === "message") {
961
+ const duplicate = this.#correlator?.observationOf(event)?.duplicate ?? false;
962
+ if (!duplicate) {
963
+ if (await this.#commands.dispatch(event)) return;
964
+ }
965
+ }
955
966
  await this.#bus.dispatch(event);
956
967
  }
957
968
  listen(route, handler) {
@@ -1030,6 +1041,9 @@ var BotRegistry = class {
1030
1041
  for (const entry of this.#bots.values()) if (entry.bot_id === key) return entry.bot;
1031
1042
  return void 0;
1032
1043
  }
1044
+ find(adapter, bot_id) {
1045
+ return this.#bots.get(connectedBotKey(adapter, bot_id))?.bot;
1046
+ }
1033
1047
  all() {
1034
1048
  return Array.from(this.#bots.values()).map((entry) => entry.bot);
1035
1049
  }
@@ -1064,9 +1078,13 @@ var EventBus = class {
1064
1078
  #nextOrder = 0;
1065
1079
  #dispatched = 0;
1066
1080
  #logger = null;
1081
+ #filter = null;
1067
1082
  setLogger(logger) {
1068
1083
  this.#logger = logger;
1069
1084
  }
1085
+ setFilter(filter$1) {
1086
+ this.#filter = filter$1;
1087
+ }
1070
1088
  /** 注册事件监听,返回取消注册函数 */
1071
1089
  register(route, handler, options = {}) {
1072
1090
  if (!route) throw new Error("EventBus.register: route must be a non-empty string");
@@ -1100,6 +1118,11 @@ var EventBus = class {
1100
1118
  const matched = [];
1101
1119
  for (const reg of this.#registrations) {
1102
1120
  if (reg.disposed) continue;
1121
+ if (this.#filter) try {
1122
+ if (!this.#filter(reg, event)) continue;
1123
+ } catch (err) {
1124
+ this.#logger?.("error", `Event registration filter failed for "${reg.source}"`, err);
1125
+ }
1103
1126
  if (isWildcardMatch(reg.route, event.type)) {
1104
1127
  matched.push(reg);
1105
1128
  continue;
@@ -1138,6 +1161,57 @@ var EventBus = class {
1138
1161
  }
1139
1162
  };
1140
1163
 
1164
+ //#endregion
1165
+ //#region src/runtime/permissions.ts
1166
+ const isObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1167
+ /** 从事件对象中提取发送者 id(支持裸 id / user_id / sender.user_id) */
1168
+ const toUserId = (value) => {
1169
+ if (typeof value === "number" || typeof value === "string" || typeof value === "bigint") return String(value);
1170
+ if (isObject$1(value)) {
1171
+ if ("user_id" in value) return toUserId(value.user_id);
1172
+ if ("sender" in value) {
1173
+ const sender = value.sender;
1174
+ if (isObject$1(sender) && "user_id" in sender) return toUserId(sender.user_id);
1175
+ }
1176
+ }
1177
+ return void 0;
1178
+ };
1179
+ /** 是否群消息 */
1180
+ const isGroupMessage = (event) => isObject$1(event) && event.kind === "message" && event.message_type === "group";
1181
+ /** 当前群群主(仅限所在群) */
1182
+ const isEventGroupOwner = (event) => {
1183
+ if (!isGroupMessage(event)) return false;
1184
+ return event.sender?.role === "owner";
1185
+ };
1186
+ /** 当前群群主或群管理员(仅限所在群) */
1187
+ const isEventGroupAdmin = (event) => {
1188
+ if (!isGroupMessage(event)) return false;
1189
+ const role = event.sender?.role;
1190
+ return role === "owner" || role === "admin";
1191
+ };
1192
+ /** 仅看 owners 名单:bot 主人(无群身份加成) */
1193
+ const isEventMaster$1 = (event) => {
1194
+ const id = toUserId(event);
1195
+ if (!id) return false;
1196
+ return isOwner(id);
1197
+ };
1198
+ /** 仅看 admins 名单:配置管理员 */
1199
+ const isEventAdminConfigOnly = (event) => {
1200
+ const id = toUserId(event);
1201
+ if (!id) return false;
1202
+ return isAdmin(id);
1203
+ };
1204
+ /**
1205
+ * master 或当前群群主
1206
+ */
1207
+ const isEventOwner$1 = (event) => isEventMaster$1(event) || isEventGroupOwner(event);
1208
+ /**
1209
+ * master、配置管理员、当前群群主或群管理员
1210
+ */
1211
+ const isEventAdmin$1 = (event) => isEventOwner$1(event) || isEventAdminConfigOnly(event) || isEventGroupAdmin(event);
1212
+ const isEventOwnerOrAdmin$1 = (event) => isEventAdmin$1(event);
1213
+ const hasEventRight$1 = (event) => isEventOwnerOrAdmin$1(event);
1214
+
1141
1215
  //#endregion
1142
1216
  //#region src/utils.ts
1143
1217
  const ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/131.0.0.0";
@@ -1904,130 +1978,19 @@ const Services = {
1904
1978
  };
1905
1979
 
1906
1980
  //#endregion
1907
- //#region src/runtime/cross-adapter-dedup.ts
1908
- const PRUNE_INTERVAL = 128;
1909
- const MESSAGE_TTL_MS = 15e3;
1910
- const EVENT_TTL_MS = 6e4;
1911
- const MAX_SIZE = 4096;
1912
- const MAX_SEGMENTS = 16;
1913
- const MAX_TEXT = 256;
1914
- const contentFingerprintOf = (event) => {
1915
- const parts = [];
1916
- for (const seg of event.message) {
1917
- if (parts.length >= MAX_SEGMENTS) break;
1918
- if (seg.type === "reply") continue;
1919
- const data = seg.data ?? {};
1920
- if (seg.type === "text") {
1921
- const text$1 = typeof data.text === "string" ? data.text : "";
1922
- parts.push(`t:${text$1.slice(0, MAX_TEXT)}`);
1923
- } else if (seg.type === "at") {
1924
- const target = data.qq ?? data.target;
1925
- parts.push(`a:${target == null ? "" : String(target)}`);
1926
- } else if (seg.type === "face") parts.push("f");
1927
- else parts.push(seg.type);
1928
- }
1929
- return parts.join("|");
1930
- };
1931
- const scopeKeyOf = (event, content) => [
1932
- event.message_type ?? "",
1933
- event.sender?.nickname?.trim() || event.user_id || "",
1934
- content
1935
- ].join("|");
1936
- const noticeKeyOf = (event) => [
1937
- event.kind,
1938
- event.identity.event_type,
1939
- event.notice_type ?? "",
1940
- event.sub_type ?? "",
1941
- event.group_id ?? "",
1942
- event.user_id ?? "",
1943
- event.operator_id ?? "",
1944
- event.identity.fingerprint ?? "",
1945
- event.identity.timestamp == null ? "" : Math.floor(event.identity.timestamp / 1e3)
1946
- ].join("|");
1947
- const requestKeyOf = (event) => [
1948
- event.kind,
1949
- event.identity.event_type,
1950
- event.request_type ?? "",
1951
- event.sub_type ?? "",
1952
- event.group_id ?? "",
1953
- event.user_id ?? "",
1954
- event.comment ?? "",
1955
- event.identity.fingerprint ?? "",
1956
- event.identity.timestamp == null ? "" : Math.floor(event.identity.timestamp / 1e3)
1957
- ].join("|");
1981
+ //#region src/runtime/mioku-context.ts
1982
+ /** 仅看 owners 名单:bot 主人(无群身份加成) */
1983
+ const isEventMaster = (event) => isEventMaster$1(event);
1958
1984
  /**
1959
- * 跨适配器消息去重(L2)
1985
+ * master 或当前群群主。等价于 `ctx.command({ permission: "owner" })` 的判断。
1960
1986
  */
1961
- var CrossAdapterEventDeduplicator = class {
1962
- #entries = new Map();
1963
- #inserts = 0;
1964
- #dropped = 0;
1965
- /** 本次运行以来被丢弃的跨适配器重复消息数 */
1966
- get dropped() {
1967
- return this.#dropped;
1968
- }
1969
- isDuplicate(event) {
1970
- let key;
1971
- let ttl;
1972
- if (event.kind === "message") {
1973
- key = scopeKeyOf(event, contentFingerprintOf(event));
1974
- ttl = MESSAGE_TTL_MS;
1975
- } else if (event.kind === "notice") {
1976
- key = noticeKeyOf(event);
1977
- ttl = EVENT_TTL_MS;
1978
- } else if (event.kind === "request") {
1979
- key = requestKeyOf(event);
1980
- ttl = EVENT_TTL_MS;
1981
- } else return false;
1982
- const now = Date.now();
1983
- const existing = this.#entries.get(key);
1984
- if (existing?.expiresAt != null && existing.expiresAt >= now) {
1985
- this.#dropped++;
1986
- return true;
1987
- }
1988
- this.#entries.set(key, { expiresAt: now + ttl });
1989
- this.#inserts++;
1990
- if (this.#inserts >= PRUNE_INTERVAL || this.#entries.size > MAX_SIZE) {
1991
- this.#inserts = 0;
1992
- this.#prune(now);
1993
- }
1994
- return false;
1995
- }
1996
- #prune(now) {
1997
- for (const [key, entry] of this.#entries) if (entry.expiresAt < now) this.#entries.delete(key);
1998
- }
1999
- clear() {
2000
- this.#entries.clear();
2001
- }
2002
- };
2003
-
2004
- //#endregion
2005
- //#region src/runtime/mioku-context.ts
2006
- const isObject$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2007
- /** 从事件对象中提取发送者 id(支持裸 id / user_id / sender.user_id) */
2008
- const toUserId = (value) => {
2009
- if (typeof value === "number" || typeof value === "string" || typeof value === "bigint") return String(value);
2010
- if (isObject$1(value)) {
2011
- if ("user_id" in value) return toUserId(value.user_id);
2012
- if ("sender" in value) {
2013
- const sender = value.sender;
2014
- if (isObject$1(sender) && "user_id" in sender) return toUserId(sender.user_id);
2015
- }
2016
- }
2017
- return void 0;
2018
- };
2019
- const isEventOwner = (event) => {
2020
- const id = toUserId(event);
2021
- if (!id) return false;
2022
- return isOwner(id);
2023
- };
2024
- const isEventAdmin = (event) => {
2025
- const id = toUserId(event);
2026
- if (!id) return false;
2027
- return isAdmin(id);
2028
- };
2029
- const isEventOwnerOrAdmin = (event) => isEventOwner(event) || isEventAdmin(event);
2030
- const hasEventRight = (event) => isEventOwnerOrAdmin(event);
1987
+ const isEventOwner = (event) => isEventOwner$1(event);
1988
+ /**
1989
+ * master、配置管理员、当前群群主或群管理员。等价于 `ctx.command({ permission: "admin" })` 的判断。
1990
+ */
1991
+ const isEventAdmin = (event) => isEventAdmin$1(event);
1992
+ const isEventOwnerOrAdmin = (event) => isEventOwnerOrAdmin$1(event);
1993
+ const hasEventRight = (event) => hasEventRight$1(event);
2031
1994
  const CTX_UTILS = {
2032
1995
  localeDate,
2033
1996
  localeTime,
@@ -2082,6 +2045,9 @@ var MiokuContext = class {
2082
2045
  get pluginName() {
2083
2046
  return this.#options.pluginName;
2084
2047
  }
2048
+ get pluginId() {
2049
+ return this.#options.pluginId;
2050
+ }
2085
2051
  /** 第一个已连接的 bot(多数单 bot 场景直接用这个) */
2086
2052
  get bot() {
2087
2053
  return this.#options.bots.all()[0];
@@ -2098,6 +2064,59 @@ var MiokuContext = class {
2098
2064
  return this.#options.bots.pick(bot_id);
2099
2065
  }
2100
2066
  /**
2067
+ * 取事件所属的跨适配器关联记录。
2068
+ * 同一条逻辑消息被多个适配器/bot 投递时,可从这里读到全部参与方。
2069
+ */
2070
+ correlation(event) {
2071
+ return this.#options.correlator?.observationOf(event)?.record;
2072
+ }
2073
+ /**
2074
+ * 观察到该事件的全部已连接 bot(首个即 primary)。
2075
+ * 跨适配器去重后,被标记为重复的投递不会进入普通 handler,
2076
+ * 但这里依然能看到全部参与方。
2077
+ */
2078
+ botsForEvent(event) {
2079
+ const record = this.correlation(event);
2080
+ if (!record) {
2081
+ const bot = event.bot;
2082
+ return bot ? [bot] : [];
2083
+ }
2084
+ const resolved = [];
2085
+ for (const participant of record.participants) {
2086
+ const bot = participant.botId ? this.#options.bots.find(participant.adapter, participant.botId) ?? this.#options.bots.pick(participant.botId) : void 0;
2087
+ if (bot && !resolved.includes(bot)) resolved.push(bot);
2088
+ }
2089
+ return resolved;
2090
+ }
2091
+ /** 事件里被 @ 到的、且本运行时已连接的 bot */
2092
+ mentionedBots(event) {
2093
+ if (event.kind !== "message") return [];
2094
+ const targets = new Set();
2095
+ for (const segment$1 of event.message) {
2096
+ if (segment$1.type !== "at") continue;
2097
+ const data = segment$1.data ?? {};
2098
+ const target = data.qq ?? data.target;
2099
+ if (target != null && String(target) !== "") targets.add(String(target));
2100
+ }
2101
+ if (targets.size === 0) return [];
2102
+ return this.#options.bots.all().filter((bot) => targets.has(String(bot.bot_id)));
2103
+ }
2104
+ /**
2105
+ * 这条消息应当由哪个 bot 回应:优先「被 @ 的 bot」,其次是关联组里首个到达的 bot。
2106
+ * 插件可用它替代隐式的 `event.bot`,从而不受适配器到达顺序影响。
2107
+ */
2108
+ pickReplyBot(event) {
2109
+ const mentioned = this.mentionedBots(event);
2110
+ if (mentioned.length > 0) return mentioned[0];
2111
+ const participants = this.botsForEvent(event);
2112
+ if (participants.length > 0) return participants[0];
2113
+ return event.bot;
2114
+ }
2115
+ /** 事件关联/去重的运行统计 */
2116
+ correlationStats() {
2117
+ return this.#options.correlator?.stats();
2118
+ }
2119
+ /**
2101
2120
  * 获取事件引用回复的消息内容。
2102
2121
  * 返回 null 表示没有引用、拿不到对应 bot 或消息已失效。
2103
2122
  */
@@ -2128,12 +2147,12 @@ var MiokuContext = class {
2128
2147
  const inputRoutes = Array.isArray(route) ? route : [route];
2129
2148
  const bypassDedup = inputRoutes.some((item) => item.startsWith("!"));
2130
2149
  const routes = inputRoutes.map((item) => item.startsWith("!") ? item.slice(1) : item);
2131
- const source = `plugin:${this.#options.pluginName}`;
2150
+ const source = `plugin:${this.#options.pluginId}`;
2132
2151
  const handledEvents = new WeakSet();
2133
- const dedup = bypassDedup || this.#options.dedup === false ? null : new CrossAdapterEventDeduplicator();
2152
+ const correlator = bypassDedup ? void 0 : this.#options.correlator;
2134
2153
  const wrappedHandler = async (event) => {
2135
2154
  if (handledEvents.has(event)) return;
2136
- if (dedup?.isDuplicate(event)) return;
2155
+ if (correlator?.isDuplicate(event)) return;
2137
2156
  handledEvents.add(event);
2138
2157
  await handler(event);
2139
2158
  };
@@ -2150,6 +2169,17 @@ var MiokuContext = class {
2150
2169
  this.#addCleanup(dispose);
2151
2170
  return dispose;
2152
2171
  }
2172
+ command(definitionOrMatcher, handler, options = {}) {
2173
+ const dispose = typeof definitionOrMatcher === "object" && definitionOrMatcher !== null && "name" in definitionOrMatcher ? this.#options.commands.register(this.#options.pluginId, {
2174
+ ...definitionOrMatcher,
2175
+ priority: definitionOrMatcher.priority ?? this.#options.priority
2176
+ }, this) : this.#options.commands.register(this.#options.pluginId, definitionOrMatcher, handler, {
2177
+ ...options,
2178
+ priority: options.priority ?? this.#options.priority
2179
+ }, this);
2180
+ this.#addCleanup(dispose);
2181
+ return dispose;
2182
+ }
2153
2183
  /** 注册 cron 定时任务,插件卸载时自动停止 */
2154
2184
  cron(expression, handler) {
2155
2185
  const task = nodeCron.schedule(expression, async (taskContext) => {
@@ -2206,6 +2236,10 @@ var MiokuContext = class {
2206
2236
  get buses() {
2207
2237
  return this.#options.bus;
2208
2238
  }
2239
+ /** 当前运行时的命令管理器 */
2240
+ get commands() {
2241
+ return this.#options.commands;
2242
+ }
2209
2243
  get segment() {
2210
2244
  return segment;
2211
2245
  }
@@ -2246,24 +2280,30 @@ var MiokuContext = class {
2246
2280
  return noticeAdmins(this.#options.bots.all(), this.#options.config.admins, message, options);
2247
2281
  }
2248
2282
  isGroupMsg(event) {
2249
- return isObject$1(event) && event.kind === "message" && event.message_type === "group";
2283
+ return isObject(event) && event.kind === "message" && event.message_type === "group";
2250
2284
  }
2251
2285
  isPrivateMsg(event) {
2252
- return isObject$1(event) && event.kind === "message" && event.message_type === "private";
2286
+ return isObject(event) && event.kind === "message" && event.message_type === "private";
2287
+ }
2288
+ /** 事件发送者是否为 bot 主人(仅看 owners 名单,不识别群身份) */
2289
+ isMaster(event) {
2290
+ return isEventMaster(event);
2253
2291
  }
2254
- /** 事件发送者是否为主人 */
2292
+ /** 事件发送者是否为 bot 主人或当前群群主;等价于 `permission: "owner"` */
2255
2293
  isOwner(event) {
2256
2294
  return isEventOwner(event);
2257
2295
  }
2258
- /** 事件发送者是否为管理员 */
2296
+ /** 事件发送者是否为 bot 主人、配置管理员、当前群群主或群管理员;等价于 `permission: "admin"` */
2259
2297
  isAdmin(event) {
2260
2298
  return isEventAdmin(event);
2261
2299
  }
2300
+ /** 同 `isAdmin`,保留旧名称 */
2262
2301
  isOwnerOrAdmin(event) {
2263
- return isEventOwnerOrAdmin(event);
2302
+ return isEventAdmin(event);
2264
2303
  }
2304
+ /** 同 `isAdmin`,别名 */
2265
2305
  hasRight(event) {
2266
- return hasEventRight(event);
2306
+ return isEventAdmin(event);
2267
2307
  }
2268
2308
  async dispose() {
2269
2309
  const all = Array.from(this.#cleanup);
@@ -2274,19 +2314,667 @@ var MiokuContext = class {
2274
2314
  }
2275
2315
  };
2276
2316
 
2317
+ //#endregion
2318
+ //#region src/runtime/event-correlator.ts
2319
+ const MESSAGE_TTL_MS = 15e3;
2320
+ const EVENT_TTL_MS = 6e4;
2321
+ const PRUNE_INTERVAL = 128;
2322
+ const MAX_SIZE = 4096;
2323
+ const MAX_SEGMENTS = 16;
2324
+ const MAX_TEXT = 256;
2325
+ const HEX32 = /[0-9a-f]{32}/i;
2326
+ const HEX40 = /[0-9a-f]{40}/i;
2327
+ const MEDIA_URL_QUERY_KEYS = [
2328
+ "fileid",
2329
+ "file_id",
2330
+ "md5",
2331
+ "fid"
2332
+ ];
2333
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2334
+ const hashString = (input) => createHash("sha1").update(input).digest("hex").slice(0, 20);
2335
+ /** 稳定序列化:对象键排序、无多余空白,忽略跨适配器的排版差异 */
2336
+ const canonicalJson = (value) => {
2337
+ if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
2338
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
2339
+ const obj = value;
2340
+ return `{${Object.keys(obj).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(obj[key])}`).join(",")}}`;
2341
+ };
2342
+ /** 结构化载荷(json/xml/ark)的稳定摘要 */
2343
+ const structuredDigest = (raw) => {
2344
+ let value = raw;
2345
+ if (typeof raw === "string") {
2346
+ const trimmed = raw.trim();
2347
+ if (!trimmed) return "";
2348
+ try {
2349
+ value = JSON.parse(trimmed);
2350
+ } catch {
2351
+ return `s:${hashString(trimmed.slice(0, MAX_TEXT * 4))}`;
2352
+ }
2353
+ }
2354
+ return `j:${hashString(canonicalJson(value))}`;
2355
+ };
2356
+ /** 去掉会过期/随会话变化的查询参数,保留跨适配器稳定的媒体定位 */
2357
+ const normalizeMediaUrl = (raw) => {
2358
+ try {
2359
+ const url = new URL(raw);
2360
+ const kept = new URLSearchParams();
2361
+ for (const key of MEDIA_URL_QUERY_KEYS) {
2362
+ const value = url.searchParams.get(key);
2363
+ if (value) kept.set(key, value);
2364
+ }
2365
+ const query = kept.toString();
2366
+ return `${url.host}${url.pathname}${query ? `?${query}` : ""}`;
2367
+ } catch {
2368
+ return raw;
2369
+ }
2370
+ };
2371
+ const firstHex = (value, pattern) => {
2372
+ if (typeof value !== "string" || !value) return void 0;
2373
+ const match$1 = value.match(pattern);
2374
+ return match$1 ? match$1[0].toLowerCase() : void 0;
2375
+ };
2376
+ /**
2377
+ * 媒体段的内容摘要:优先内容哈希(md5/sha1),其次是文件名里嵌的 md5,
2378
+ * 再次是规范化 URL。都拿不到时返回空串,由调用方退化为「只看段类型」。
2379
+ */
2380
+ const mediaDigest = (data) => {
2381
+ const md5$1 = firstHex(data.md5, HEX32);
2382
+ if (md5$1) return `h:${md5$1}`;
2383
+ const sha1 = firstHex(data.sha1, HEX40);
2384
+ if (sha1) return `h:${sha1}`;
2385
+ for (const key of [
2386
+ "file_unique",
2387
+ "file_id",
2388
+ "fid",
2389
+ "id",
2390
+ "file",
2391
+ "name"
2392
+ ]) {
2393
+ const hex = firstHex(data[key], HEX32);
2394
+ if (hex) return `h:${hex}`;
2395
+ }
2396
+ const url = data.url;
2397
+ if (typeof url === "string" && url) return `u:${normalizeMediaUrl(url)}`;
2398
+ for (const key of [
2399
+ "file",
2400
+ "file_id",
2401
+ "fid",
2402
+ "id",
2403
+ "name"
2404
+ ]) {
2405
+ const value = data[key];
2406
+ if (typeof value === "string" && value) return `r:${value.toLowerCase()}`;
2407
+ }
2408
+ return "";
2409
+ };
2410
+ /** 只在载荷跨协议一致时才使用摘要的段类型 */
2411
+ const DIGEST_MEDIA_TYPES = new Set(["image", "flash"]);
2412
+ const segmentSignature = (segment$1) => {
2413
+ const data = isRecord(segment$1.data) ? segment$1.data : {};
2414
+ switch (segment$1.type) {
2415
+ case "text": {
2416
+ const text$1 = typeof data.text === "string" ? data.text : "";
2417
+ return `t:${text$1.slice(0, MAX_TEXT)}`;
2418
+ }
2419
+ case "at": {
2420
+ const target = data.qq ?? data.target;
2421
+ return `a:${target == null ? "" : String(target)}`;
2422
+ }
2423
+ case "face": {
2424
+ const id = data.id ?? data.face;
2425
+ return `f:${id == null ? "" : String(id)}`;
2426
+ }
2427
+ case "json":
2428
+ case "xml":
2429
+ case "ark": {
2430
+ const digest = structuredDigest(data.data ?? data.content ?? data);
2431
+ return digest ? `${segment$1.type}:${digest}` : segment$1.type;
2432
+ }
2433
+ default: {
2434
+ if (DIGEST_MEDIA_TYPES.has(segment$1.type)) {
2435
+ const digest = mediaDigest(data);
2436
+ return digest ? `${segment$1.type}:${digest}` : segment$1.type;
2437
+ }
2438
+ return segment$1.type;
2439
+ }
2440
+ }
2441
+ };
2442
+ const contentSignatureOf = (event) => {
2443
+ const parts = [];
2444
+ for (const segment$1 of event.message) {
2445
+ if (parts.length >= MAX_SEGMENTS) break;
2446
+ if (segment$1.type === "reply") continue;
2447
+ parts.push(segmentSignature(segment$1));
2448
+ }
2449
+ return parts.join("|");
2450
+ };
2451
+ const messageKeyOf = (event) => {
2452
+ const type = event.message_type ?? "";
2453
+ const scope = event.group_id ?? event.user_id ?? "";
2454
+ const sender = event.user_id ?? event.sender?.user_id ?? "";
2455
+ return [
2456
+ "m",
2457
+ type,
2458
+ scope,
2459
+ sender,
2460
+ contentSignatureOf(event)
2461
+ ].join("|");
2462
+ };
2463
+ const noticeKeyOf = (event) => [
2464
+ "n",
2465
+ event.identity.event_type,
2466
+ event.notice_type ?? "",
2467
+ event.sub_type ?? "",
2468
+ event.group_id ?? "",
2469
+ event.user_id ?? "",
2470
+ event.operator_id ?? "",
2471
+ event.identity.timestamp == null ? "" : Math.floor(event.identity.timestamp / 1e3)
2472
+ ].join("|");
2473
+ const requestKeyOf = (event) => [
2474
+ "r",
2475
+ event.identity.event_type,
2476
+ event.request_type ?? "",
2477
+ event.sub_type ?? "",
2478
+ event.group_id ?? "",
2479
+ event.user_id ?? "",
2480
+ event.comment ?? "",
2481
+ event.identity.timestamp == null ? "" : Math.floor(event.identity.timestamp / 1e3)
2482
+ ].join("|");
2483
+ const participantOf = (event) => ({
2484
+ adapter: event.identity.adapter,
2485
+ botId: event.identity.bot_id,
2486
+ eventType: event.identity.event_type,
2487
+ messageId: event.identity.message_id ?? event.identity.native_event_id
2488
+ });
2489
+ const sameParticipant = (a, b) => a.adapter === b.adapter && (a.botId ?? "") === (b.botId ?? "") && (a.messageId ?? "") === (b.messageId ?? "");
2490
+ /**
2491
+ * 跨适配器事件关联器
2492
+ */
2493
+ var EventCorrelator = class {
2494
+ #groups = new Map();
2495
+ #byEvent = new WeakMap();
2496
+ #logger;
2497
+ #maxSize;
2498
+ #inserts = 0;
2499
+ #observed = 0;
2500
+ #duplicates = 0;
2501
+ #evicted = 0;
2502
+ constructor(options = {}) {
2503
+ this.#logger = options.logger;
2504
+ this.#maxSize = options.maxSize ?? MAX_SIZE;
2505
+ }
2506
+ /** 观察一个事件,登记它所属的关联组并返回其定位(同一事件只登记一次) */
2507
+ observe(event) {
2508
+ const cached = this.#byEvent.get(event);
2509
+ if (cached) return cached;
2510
+ const located = this.#locate(event);
2511
+ if (!located) return null;
2512
+ const { key, ttl } = located;
2513
+ const now = Date.now();
2514
+ this.#observed++;
2515
+ let group = this.#groups.get(key);
2516
+ let duplicate = false;
2517
+ if (group && group.expiresAt >= now) {
2518
+ duplicate = true;
2519
+ this.#duplicates++;
2520
+ const participant = participantOf(event);
2521
+ if (!group.participants.some((item) => sameParticipant(item, participant))) group.participants.push(participant);
2522
+ this.#logger?.debug(`去重命中: key=${key} adapter=${participant.adapter} bot=${participant.botId ?? ""} primary=${group.primary.adapter}:${group.primary.botId ?? ""}`);
2523
+ } else {
2524
+ const participant = participantOf(event);
2525
+ group = {
2526
+ key,
2527
+ expiresAt: now + ttl,
2528
+ firstSeenAt: now,
2529
+ primary: participant,
2530
+ participants: [participant]
2531
+ };
2532
+ this.#groups.set(key, group);
2533
+ this.#inserts++;
2534
+ if (this.#inserts >= PRUNE_INTERVAL || this.#groups.size > this.#maxSize) {
2535
+ this.#inserts = 0;
2536
+ this.#prune(now);
2537
+ }
2538
+ }
2539
+ const observation = {
2540
+ record: group,
2541
+ primary: !duplicate,
2542
+ duplicate
2543
+ };
2544
+ this.#byEvent.set(event, observation);
2545
+ return observation;
2546
+ }
2547
+ /** 读取某事件此前登记的关联定位(未登记或已过期时返回 undefined) */
2548
+ observationOf(event) {
2549
+ const observation = this.#byEvent.get(event);
2550
+ if (!observation) return void 0;
2551
+ if (observation.record.expiresAt < Date.now()) return void 0;
2552
+ return observation;
2553
+ }
2554
+ /** 该事件是否属于关联组里后续到达的重复投递(未登记时会先登记) */
2555
+ isDuplicate(event) {
2556
+ return this.observe(event)?.duplicate === true;
2557
+ }
2558
+ stats() {
2559
+ return {
2560
+ groups: this.#groups.size,
2561
+ observed: this.#observed,
2562
+ duplicates: this.#duplicates,
2563
+ evicted: this.#evicted
2564
+ };
2565
+ }
2566
+ clear() {
2567
+ this.#groups.clear();
2568
+ }
2569
+ #locate(event) {
2570
+ if (event.kind === "message") return {
2571
+ key: messageKeyOf(event),
2572
+ ttl: MESSAGE_TTL_MS
2573
+ };
2574
+ if (event.kind === "notice") return {
2575
+ key: noticeKeyOf(event),
2576
+ ttl: EVENT_TTL_MS
2577
+ };
2578
+ if (event.kind === "request") return {
2579
+ key: requestKeyOf(event),
2580
+ ttl: EVENT_TTL_MS
2581
+ };
2582
+ return null;
2583
+ }
2584
+ #prune(now) {
2585
+ for (const [key, group] of this.#groups) if (group.expiresAt < now) this.#groups.delete(key);
2586
+ if (this.#groups.size <= this.#maxSize) return;
2587
+ const drop = this.#groups.size - this.#maxSize;
2588
+ let dropped = 0;
2589
+ for (const key of this.#groups.keys()) {
2590
+ this.#groups.delete(key);
2591
+ this.#evicted++;
2592
+ if (++dropped >= drop) break;
2593
+ }
2594
+ }
2595
+ };
2596
+
2597
+ //#endregion
2598
+ //#region src/runtime/commands.ts
2599
+ const normalizeRole = (role) => {
2600
+ const value = String(role ?? "member").trim().toLowerCase();
2601
+ if (value === "master" || value === "owner" || value === "admin") return value;
2602
+ return "member";
2603
+ };
2604
+ const userIdOf = (event) => toUserId(event);
2605
+ const textHookMatches = (text$1, raw) => {
2606
+ const source = String(raw ?? "").trim();
2607
+ if (!source) return false;
2608
+ if (source.startsWith("/") && source.length > 1) {
2609
+ const end = source.lastIndexOf("/");
2610
+ if (end > 0) try {
2611
+ const flags = source.slice(end + 1);
2612
+ const regex = new RegExp(source.slice(1, end), flags);
2613
+ return regex.test(text$1);
2614
+ } catch {
2615
+ return false;
2616
+ }
2617
+ }
2618
+ return text$1 === source || text$1.startsWith(source);
2619
+ };
2620
+ const routeMatches = (event, route) => {
2621
+ if (!route) return false;
2622
+ return event.type === route || event.routes.some((item) => item === route);
2623
+ };
2624
+ const parsePrefixes = (prefixes, defaultPrefix) => {
2625
+ if (prefixes === false) return ["", defaultPrefix];
2626
+ const values = Array.isArray(prefixes) ? prefixes : [prefixes];
2627
+ const normalized = values.map((value) => String(value)).filter((value, index, all) => all.indexOf(value) === index);
2628
+ return normalized.length > 0 ? normalized : [defaultPrefix];
2629
+ };
2630
+ const commandBody = (text$1, prefixes) => {
2631
+ const prefix = [...prefixes].sort((a, b) => b.length - a.length).find((item) => item === "" || text$1.startsWith(item));
2632
+ if (prefix === void 0) return void 0;
2633
+ return text$1.slice(prefix.length).trimStart();
2634
+ };
2635
+ const stringMatch = (body, matcher) => {
2636
+ const value = matcher.trim();
2637
+ if (!value) return void 0;
2638
+ if (body !== value && !(body.startsWith(value) && /\s/.test(body[value.length] ?? ""))) return void 0;
2639
+ const rest = body.slice(value.length).trim();
2640
+ return { args: rest ? rest.split(/\s+/) : [] };
2641
+ };
2642
+ const commandKey$1 = (value) => String(value).trim().replace(/^[#/.]+\s*/, "").split(/\s+/)[0].toLowerCase();
2643
+ const displayCommand = (command, defaultPrefix) => {
2644
+ if (command.prefixes === false) return command.name;
2645
+ const prefixes = command.prefixes === void 0 ? [defaultPrefix] : Array.isArray(command.prefixes) ? command.prefixes : [command.prefixes];
2646
+ return `${prefixes.find((prefix) => prefix.length > 0) ?? ""}${command.name}`;
2647
+ };
2648
+ var CommandManager = class {
2649
+ #getDefaultPrefix;
2650
+ #getOwners;
2651
+ #getAdmins;
2652
+ #logger;
2653
+ #commands = [];
2654
+ #legacy = new Map();
2655
+ #access;
2656
+ #nextOrder = 0;
2657
+ constructor(options) {
2658
+ this.#getDefaultPrefix = options.getDefaultPrefix;
2659
+ this.#getOwners = options.getOwners;
2660
+ this.#getAdmins = options.getAdmins;
2661
+ this.#logger = options.logger;
2662
+ }
2663
+ register(plugin, definitionOrMatcher, handlerOrContext, optionsOrContext = {}, context) {
2664
+ const isDefinition = typeof definitionOrMatcher === "object" && definitionOrMatcher !== null && "name" in definitionOrMatcher;
2665
+ const shortcutOptions = isDefinition ? {} : optionsOrContext;
2666
+ const resolvedContext = isDefinition ? handlerOrContext : context;
2667
+ const handler = isDefinition ? void 0 : handlerOrContext;
2668
+ const definition = isDefinition ? definitionOrMatcher : {
2669
+ ...shortcutOptions,
2670
+ name: shortcutOptions.name ?? String(definitionOrMatcher),
2671
+ match: definitionOrMatcher,
2672
+ handler
2673
+ };
2674
+ if (typeof definition.handler !== "function") throw new Error("CommandManager.register: handler must be a function");
2675
+ if (!resolvedContext) throw new Error("CommandManager.register: context is required");
2676
+ const name = String(definition.name ?? "").trim();
2677
+ if (!name) throw new Error("CommandManager.register: name is required");
2678
+ const matcher = definition.match ?? name;
2679
+ const command = {
2680
+ plugin,
2681
+ id: String(definition.id ?? name).trim() || name,
2682
+ name,
2683
+ aliases: (definition.aliases ?? []).map((item) => String(item).trim()).filter(Boolean),
2684
+ matcher,
2685
+ description: String(definition.description ?? "").trim(),
2686
+ usage: definition.usage,
2687
+ permission: normalizeRole(definition.permission),
2688
+ priority: typeof definition.priority === "number" && !Number.isNaN(definition.priority) ? definition.priority : 0,
2689
+ prefixes: definition.prefixes,
2690
+ handler: definition.handler,
2691
+ order: this.#nextOrder++,
2692
+ context: resolvedContext
2693
+ };
2694
+ this.#commands.push(command);
2695
+ return () => {
2696
+ const index = this.#commands.indexOf(command);
2697
+ if (index >= 0) this.#commands.splice(index, 1);
2698
+ };
2699
+ }
2700
+ setAccessControl(config) {
2701
+ this.#access = config;
2702
+ }
2703
+ syncMetadata(metadata) {
2704
+ this.#legacy.set(metadata.name, { metadata });
2705
+ }
2706
+ removePlugin(plugin) {
2707
+ this.#legacy.delete(plugin);
2708
+ for (let i = this.#commands.length - 1; i >= 0; i--) if (this.#commands[i].plugin === plugin) this.#commands.splice(i, 1);
2709
+ }
2710
+ clear() {
2711
+ this.#commands.length = 0;
2712
+ this.#legacy.clear();
2713
+ this.#access = void 0;
2714
+ }
2715
+ list() {
2716
+ return this.#orderedCommands();
2717
+ }
2718
+ #orderedCommands() {
2719
+ return [...this.#commands].sort((a, b) => a.priority - b.priority || a.order - b.order);
2720
+ }
2721
+ getPluginHelp(plugin) {
2722
+ const metadata = this.#legacy.get(plugin)?.metadata;
2723
+ const runtime = this.#commands.filter((item) => item.plugin === plugin);
2724
+ if (!metadata?.config.help && runtime.length === 0) return void 0;
2725
+ const commands = [];
2726
+ const seen = new Set();
2727
+ const runtimeByKey = new Map(runtime.map((command) => [commandKey$1(command.name), command]));
2728
+ for (const command of metadata?.config.help?.commands ?? []) {
2729
+ const key = commandKey$1(command.cmd);
2730
+ const runtimeCommand = runtimeByKey.get(key);
2731
+ if (runtimeCommand) {
2732
+ const cmd = displayCommand(runtimeCommand, this.#getDefaultPrefix());
2733
+ seen.add(key);
2734
+ commands.push({
2735
+ cmd,
2736
+ desc: runtimeCommand.description || command.desc || cmd,
2737
+ usage: runtimeCommand.usage ?? command.usage,
2738
+ role: runtimeCommand.permission
2739
+ });
2740
+ } else if (!seen.has(key)) {
2741
+ seen.add(key);
2742
+ commands.push(command);
2743
+ }
2744
+ }
2745
+ for (const command of runtime) {
2746
+ const cmd = displayCommand(command, this.#getDefaultPrefix());
2747
+ if (seen.has(commandKey$1(cmd))) continue;
2748
+ seen.add(commandKey$1(cmd));
2749
+ commands.push({
2750
+ cmd,
2751
+ desc: command.description || cmd,
2752
+ usage: command.usage,
2753
+ role: command.permission
2754
+ });
2755
+ }
2756
+ const help = metadata?.config.help;
2757
+ return {
2758
+ title: help?.title || plugin,
2759
+ description: help?.description || metadata?.description || "",
2760
+ commands
2761
+ };
2762
+ }
2763
+ catalog() {
2764
+ const items = [];
2765
+ const plugins = new Set([...this.#legacy.keys(), ...this.#commands.map((item) => item.plugin)]);
2766
+ for (const plugin of plugins) {
2767
+ const metadata = this.#legacy.get(plugin)?.metadata;
2768
+ const help = metadata?.config.help;
2769
+ items.push({
2770
+ kind: "plugin",
2771
+ plugin,
2772
+ id: plugin,
2773
+ label: help?.title || plugin,
2774
+ desc: help?.description || metadata?.description,
2775
+ source: metadata ? "manifest" : "runtime"
2776
+ });
2777
+ const seen = new Set();
2778
+ for (const command of this.#commands.filter((item) => item.plugin === plugin)) {
2779
+ seen.add(commandKey$1(command.id));
2780
+ items.push({
2781
+ kind: "command",
2782
+ plugin,
2783
+ id: command.id,
2784
+ label: command.name,
2785
+ desc: command.description,
2786
+ usage: command.usage,
2787
+ permission: command.permission,
2788
+ priority: command.priority,
2789
+ match: command.matcher instanceof RegExp ? command.matcher.toString() : command.matcher,
2790
+ source: "runtime"
2791
+ });
2792
+ }
2793
+ for (const hook of metadata?.config.accessHooks ?? []) {
2794
+ if (seen.has(commandKey$1(hook.id))) continue;
2795
+ seen.add(commandKey$1(hook.id));
2796
+ items.push({
2797
+ kind: "command",
2798
+ plugin,
2799
+ id: hook.id,
2800
+ label: hook.id,
2801
+ desc: hook.description,
2802
+ match: hook.match,
2803
+ event: hook.event,
2804
+ source: "manifest"
2805
+ });
2806
+ }
2807
+ for (const command of help?.commands ?? []) {
2808
+ if (seen.has(commandKey$1(command.cmd))) continue;
2809
+ seen.add(commandKey$1(command.cmd));
2810
+ items.push({
2811
+ kind: "command",
2812
+ plugin,
2813
+ id: command.cmd,
2814
+ label: command.cmd,
2815
+ desc: command.desc,
2816
+ usage: command.usage,
2817
+ permission: command.role,
2818
+ source: "manifest"
2819
+ });
2820
+ }
2821
+ }
2822
+ return items.sort((a, b) => a.kind === b.kind ? a.plugin.localeCompare(b.plugin) || a.id.localeCompare(b.id) : a.kind === "plugin" ? -1 : 1);
2823
+ }
2824
+ async dispatch(event) {
2825
+ const text$1 = String(event.message?.text?.() ?? "").trim();
2826
+ if (!text$1) return false;
2827
+ const match$1 = this.#find(text$1);
2828
+ if (!match$1) return false;
2829
+ if (!this.#canUse(match$1.command, event)) return true;
2830
+ try {
2831
+ await match$1.command.handler({
2832
+ ctx: match$1.command.context,
2833
+ event,
2834
+ command: match$1.command,
2835
+ text: text$1,
2836
+ body: match$1.body,
2837
+ args: match$1.args,
2838
+ match: match$1.match
2839
+ });
2840
+ } catch (error) {
2841
+ this.#logger?.error(`Command "${match$1.command.plugin}:${match$1.command.id}" failed`, error);
2842
+ }
2843
+ return true;
2844
+ }
2845
+ shouldDispatch(source, event) {
2846
+ if (!source.startsWith("plugin:")) return true;
2847
+ const plugin = source.slice(7);
2848
+ const metadata = this.#legacy.get(plugin)?.metadata;
2849
+ if (!metadata) return this.#pluginAllowed(plugin, event);
2850
+ if (!this.#pluginAllowed(plugin, event)) return false;
2851
+ const text$1 = event.kind === "message" ? String(event.message.text()).trim() : "";
2852
+ const matches = (metadata.config.accessHooks ?? []).filter((hook) => event.kind === "message" && textHookMatches(text$1, hook.match) || routeMatches(event, hook.event));
2853
+ return matches.every((hook) => this.#commandAllowed(plugin, hook.id, event));
2854
+ }
2855
+ #find(text$1) {
2856
+ for (const command of this.#orderedCommands()) {
2857
+ const prefixes = parsePrefixes(command.prefixes ?? this.#getDefaultPrefix(), this.#getDefaultPrefix());
2858
+ const body = commandBody(text$1, prefixes);
2859
+ if (body === void 0) continue;
2860
+ if (typeof command.matcher === "string") {
2861
+ const candidates = [
2862
+ command.matcher,
2863
+ command.name,
2864
+ ...command.aliases
2865
+ ];
2866
+ for (const candidate of candidates) {
2867
+ const result = stringMatch(body, candidate);
2868
+ if (result) return {
2869
+ command,
2870
+ body,
2871
+ args: result.args
2872
+ };
2873
+ }
2874
+ continue;
2875
+ }
2876
+ command.matcher.lastIndex = 0;
2877
+ const matched = body.match(command.matcher);
2878
+ if (!matched) continue;
2879
+ const args = matched.length > 1 ? matched.slice(1).filter((item) => item != null) : body.slice(matched[0].length).trim().split(/\s+/).filter(Boolean);
2880
+ return {
2881
+ command,
2882
+ body,
2883
+ args,
2884
+ match: matched
2885
+ };
2886
+ }
2887
+ return void 0;
2888
+ }
2889
+ #canUse(command, event) {
2890
+ const userId = userIdOf(event);
2891
+ const master = Boolean(userId && this.#isMaster(userId));
2892
+ const owner = master || this.#isGroupOwner(event);
2893
+ const admin = owner || Boolean(userId && this.#isAdmin(userId)) || this.#isGroupAdmin(event);
2894
+ if (command.permission === "master") {
2895
+ if (!master) return false;
2896
+ } else if (command.permission === "owner") {
2897
+ if (!owner) return false;
2898
+ } else if (command.permission === "admin" && !admin) return false;
2899
+ return this.#commandAllowed(command.plugin, command.id, event);
2900
+ }
2901
+ #pluginAllowed(plugin, event) {
2902
+ const userId = event.kind === "message" ? userIdOf(event) : "user_id" in event ? event.user_id : void 0;
2903
+ if (userId && (this.#isMaster(userId) || this.#isAdmin(userId)) || this.#isGroupOwner(event) || this.#isGroupAdmin(event)) return true;
2904
+ return this.#resolveAction(plugin, void 0, event) !== "block";
2905
+ }
2906
+ #commandAllowed(plugin, command, event) {
2907
+ const userId = event.kind === "message" ? userIdOf(event) : "user_id" in event ? event.user_id : void 0;
2908
+ if (userId && (this.#isMaster(userId) || this.#isAdmin(userId)) || this.#isGroupOwner(event) || this.#isGroupAdmin(event)) return true;
2909
+ return this.#resolveAction(plugin, command, event) !== "block";
2910
+ }
2911
+ #resolveAction(plugin, command, event) {
2912
+ const access = this.#access;
2913
+ if (!access) return void 0;
2914
+ const scopes = [
2915
+ (() => {
2916
+ const userId = event.kind === "message" ? userIdOf(event) : "user_id" in event && event.user_id ? String(event.user_id) : void 0;
2917
+ return userId ? access.users[userId] : void 0;
2918
+ })(),
2919
+ (() => {
2920
+ const groupId = "group_id" in event && event.group_id ? String(event.group_id) : void 0;
2921
+ return groupId ? access.groups[groupId] : void 0;
2922
+ })(),
2923
+ access.global
2924
+ ];
2925
+ for (const scope of scopes) {
2926
+ if (!scope) continue;
2927
+ const commandRule = command ? scope.commands?.[plugin]?.[command]?.action : void 0;
2928
+ if (commandRule) return commandRule;
2929
+ const pluginRule = scope.plugins?.[plugin]?.action;
2930
+ if (pluginRule) return pluginRule;
2931
+ }
2932
+ return void 0;
2933
+ }
2934
+ #isMaster(userId) {
2935
+ return this.#owners().includes(userId);
2936
+ }
2937
+ #isAdmin(userId) {
2938
+ return this.#isMaster(userId) || this.#admins().includes(userId);
2939
+ }
2940
+ #isGroupOwner(event) {
2941
+ return isEventGroupOwner(event);
2942
+ }
2943
+ #isGroupAdmin(event) {
2944
+ return isEventGroupAdmin(event);
2945
+ }
2946
+ #owners() {
2947
+ return this.#configIds("owners");
2948
+ }
2949
+ #admins() {
2950
+ return this.#configIds("admins");
2951
+ }
2952
+ #configIds(key) {
2953
+ return [...key === "owners" ? this.#getOwners() : this.#getAdmins()].map(String);
2954
+ }
2955
+ };
2956
+ let activeCommandManager;
2957
+ const setActiveCommandManager = (manager) => {
2958
+ activeCommandManager = manager;
2959
+ };
2960
+ const getActiveCommandManager = () => activeCommandManager;
2961
+
2277
2962
  //#endregion
2278
2963
  //#region src/runtime/plugin-metadata.ts
2279
2964
  const store$1 = getOrCreate("plugin-metadata", () => new Map());
2280
2965
  const setPluginMetadata = (meta) => {
2281
2966
  store$1.set(meta.name, meta);
2967
+ getActiveCommandManager()?.syncMetadata(meta);
2282
2968
  };
2283
2969
  const removePluginMetadata = (name) => {
2284
2970
  store$1.delete(name);
2971
+ getActiveCommandManager()?.removePlugin(name);
2285
2972
  };
2286
2973
  const getPluginMetadata = (name) => store$1.get(name);
2287
2974
  const getPluginMetadataList = () => [...store$1.values()];
2288
2975
  const resetPluginMetadata = () => {
2289
2976
  store$1.clear();
2977
+ getActiveCommandManager()?.clear();
2290
2978
  };
2291
2979
 
2292
2980
  //#endregion
@@ -2946,12 +3634,6 @@ async function sendTextOrForward(options) {
2946
3634
  }
2947
3635
  }
2948
3636
 
2949
- //#endregion
2950
- //#region src/builtin/core/commands/prefix.ts
2951
- function getCommandPrefix() {
2952
- return botConfig.prefix ?? ".";
2953
- }
2954
-
2955
3637
  //#endregion
2956
3638
  //#region src/builtin/core/system/package-manager.ts
2957
3639
  const NPM_REGISTRY = "https://registry.npmjs.org";
@@ -3543,102 +4225,102 @@ async function performUpdateAndReport(ctx, event, names) {
3543
4225
  triggerRestart(marker);
3544
4226
  }
3545
4227
  function registerUpdateCommands(ctx) {
3546
- const dispose = ctx.handle("message", async (event) => {
3547
- const text$1 = ctx.text(event)?.trim();
3548
- if (!text$1 || event?.user_id === event?.self_id) return;
3549
- const prefix = getCommandPrefix();
3550
- if (!text$1.startsWith(`${prefix}update`)) return;
3551
- if (!isEventOwner(event)) {
3552
- ctx.logger.warn("[core] update 指令仅主人可用");
3553
- return;
3554
- }
3555
- const arg = text$1.slice(prefix.length + 6).trim();
3556
- const selfId = String(event?.self_id || "");
3557
- const bot = event.bot;
3558
- if (!bot) return;
3559
- if (arg === "all") {
3560
- const managed = await updateAllManaged();
3561
- if (managed.names.length === 0) {
3562
- await replyText(event, "未找到可更新的 mioku 包");
3563
- return;
3564
- }
3565
- await performUpdateAndReport(ctx, event, managed.names);
3566
- return;
3567
- }
3568
- if (arg === "mioku" || arg === "self") {
3569
- await performUpdateAndReport(ctx, event, ["mioku"]);
3570
- return;
3571
- }
3572
- if (arg.startsWith("plugin ") || arg.startsWith("service ")) {
3573
- const [typeRaw, ...rest] = arg.split(/\s+/);
3574
- const name = rest.join(" ").trim();
3575
- const type = typeRaw === "plugin" ? "plugin" : "service";
3576
- if (!name) {
3577
- await replyText(event, `用法:${prefix}update ${type} <名称>`);
4228
+ const dispose = ctx.command({
4229
+ name: "update",
4230
+ aliases: ["更新"],
4231
+ permission: "master",
4232
+ priority: -1e3,
4233
+ description: "检查并选择插件/服务更新",
4234
+ handler: async ({ event, args }) => {
4235
+ const prefix = String(ctx.config.prefix ?? ".");
4236
+ const arg = args.join(" ").trim();
4237
+ const selfId = String(event?.self_id || "");
4238
+ const bot = event.bot;
4239
+ if (!bot) return;
4240
+ if (arg === "all") {
4241
+ const managed = await updateAllManaged();
4242
+ if (managed.names.length === 0) {
4243
+ await replyText(event, "未找到可更新的 mioku 包");
4244
+ return;
4245
+ }
4246
+ await performUpdateAndReport(ctx, event, managed.names);
3578
4247
  return;
3579
4248
  }
3580
- const pkgPrefix = type === "plugin" ? "mioku-plugin-" : "mioku-service-";
3581
- const fullName = name.startsWith(pkgPrefix) ? name : `${pkgPrefix}${name}`;
3582
- await performUpdateAndReport(ctx, event, [fullName]);
3583
- return;
3584
- }
3585
- if (arg === "check" || arg === "检查") {
3586
- const pending = getPendingMap();
3587
- const key = conversationKey(event);
3588
- const existing = pending.get(key);
3589
- if (existing) {
3590
- existing.disposer();
3591
- clearTimeout(existing.timer);
3592
- pending.delete(key);
3593
- }
3594
- let items;
3595
- try {
3596
- items = await checkUpdates();
3597
- } catch (error) {
3598
- ctx.logger.error(`[core] 检查更新失败: ${error}`);
3599
- await replyText(event, `检查更新失败:${String(error)}`);
4249
+ if (arg === "mioku" || arg === "self") {
4250
+ await performUpdateAndReport(ctx, event, ["mioku"]);
3600
4251
  return;
3601
4252
  }
3602
- if (items.length === 0) {
3603
- await replyText(event, "所有插件与服务均已是最新版本");
4253
+ if (arg.startsWith("plugin ") || arg.startsWith("service ")) {
4254
+ const [typeRaw, ...rest] = arg.split(/\s+/);
4255
+ const name = rest.join(" ").trim();
4256
+ const type = typeRaw === "plugin" ? "plugin" : "service";
4257
+ if (!name) {
4258
+ await replyText(event, `用法:${prefix}update ${type} <名称>`);
4259
+ return;
4260
+ }
4261
+ const pkgPrefix = type === "plugin" ? "mioku-plugin-" : "mioku-service-";
4262
+ const fullName = name.startsWith(pkgPrefix) ? name : `${pkgPrefix}${name}`;
4263
+ await performUpdateAndReport(ctx, event, [fullName]);
3604
4264
  return;
3605
4265
  }
3606
- await replyText(event, renderUpdateList(items));
3607
- const timeoutMs = SELECT_TIMEOUT_MS;
3608
- const listenerDispose = ctx.handle("message", async (ev) => {
3609
- if (String(ev?.self_id || "") !== selfId) return;
3610
- if (conversationKey(ev) !== key) return;
3611
- if (!isEventOwner(ev)) return;
3612
- const evText = ctx.text(ev)?.trim() || "";
3613
- if (evText.startsWith(`${prefix}update`)) return;
3614
- const sel = pending.get(key);
3615
- if (!sel) return;
3616
- clearTimeout(sel.timer);
3617
- sel.disposer();
3618
- pending.delete(key);
3619
- const parsed = parseSelection(evText, sel.items.length);
3620
- const chosen = parsed.all ? sel.items : parsed.indices.map((i) => sel.items[i]).filter(Boolean);
3621
- if (chosen.length === 0) {
3622
- await replyText(event, "未选择任何有效项,已取消");
4266
+ if (arg === "check" || arg === "检查") {
4267
+ const pending = getPendingMap();
4268
+ const key = conversationKey(event);
4269
+ const existing = pending.get(key);
4270
+ if (existing) {
4271
+ existing.disposer();
4272
+ clearTimeout(existing.timer);
4273
+ pending.delete(key);
4274
+ }
4275
+ let items;
4276
+ try {
4277
+ items = await checkUpdates();
4278
+ } catch (error) {
4279
+ ctx.logger.error(`[core] 检查更新失败: ${error}`);
4280
+ await replyText(event, `检查更新失败:${String(error)}`);
3623
4281
  return;
3624
4282
  }
3625
- await performUpdateAndReport(ctx, event, chosen.map((i) => i.name));
3626
- });
3627
- const timer = setTimeout(async () => {
3628
- const sel = pending.get(key);
3629
- if (!sel) return;
3630
- sel.disposer();
3631
- pending.delete(key);
3632
- await replyText(event, "操作超时,已取消更新");
3633
- }, timeoutMs);
3634
- pending.set(key, {
3635
- disposer: listenerDispose,
3636
- timer,
3637
- items
3638
- });
3639
- return;
4283
+ if (items.length === 0) {
4284
+ await replyText(event, "所有插件与服务均已是最新版本");
4285
+ return;
4286
+ }
4287
+ await replyText(event, renderUpdateList(items));
4288
+ const timeoutMs = SELECT_TIMEOUT_MS;
4289
+ const listenerDispose = ctx.handle("message", async (ev) => {
4290
+ if (String(ev?.self_id || "") !== selfId) return;
4291
+ if (conversationKey(ev) !== key) return;
4292
+ if (!isEventMaster(ev)) return;
4293
+ const evText = ctx.text(ev)?.trim() || "";
4294
+ if (evText.startsWith(`${prefix}update`)) return;
4295
+ const sel = pending.get(key);
4296
+ if (!sel) return;
4297
+ clearTimeout(sel.timer);
4298
+ sel.disposer();
4299
+ pending.delete(key);
4300
+ const parsed = parseSelection(evText, sel.items.length);
4301
+ const chosen = parsed.all ? sel.items : parsed.indices.map((i) => sel.items[i]).filter(Boolean);
4302
+ if (chosen.length === 0) {
4303
+ await replyText(event, "未选择任何有效项,已取消");
4304
+ return;
4305
+ }
4306
+ await performUpdateAndReport(ctx, event, chosen.map((i) => i.name));
4307
+ });
4308
+ const timer = setTimeout(async () => {
4309
+ const sel = pending.get(key);
4310
+ if (!sel) return;
4311
+ sel.disposer();
4312
+ pending.delete(key);
4313
+ await replyText(event, "操作超时,已取消更新");
4314
+ }, timeoutMs);
4315
+ pending.set(key, {
4316
+ disposer: listenerDispose,
4317
+ timer,
4318
+ items
4319
+ });
4320
+ return;
4321
+ }
4322
+ await replyText(event, `用法:\n${prefix}update check 检查并选择更新\n${prefix}update all 更新全部\n${prefix}update mioku 更新框架\n${prefix}update plugin <名称>\n${prefix}update service <名称>`);
3640
4323
  }
3641
- await replyText(event, `用法:\n${prefix}update check 检查并选择更新\n${prefix}update all 更新全部\n${prefix}update mioku 更新框架\n${prefix}update plugin <名称>\n${prefix}update service <名称>`);
3642
4324
  });
3643
4325
  return () => {
3644
4326
  dispose();
@@ -3653,15 +4335,13 @@ function registerUpdateCommands(ctx) {
3653
4335
 
3654
4336
  //#endregion
3655
4337
  //#region src/builtin/core/commands/install.ts
3656
- function parseTargetArgs(text$1, head) {
3657
- if (!text$1.startsWith(head)) return null;
3658
- const rest = text$1.slice(head.length).trim();
3659
- const matched = rest.match(/^(plugin|service)\s+([\s\S]+)$/);
3660
- if (!matched) return null;
3661
- const name = matched[2].trim();
4338
+ function parseTargetArgs(args) {
4339
+ const type = args[0];
4340
+ if (type !== "plugin" && type !== "service") return null;
4341
+ const name = args.slice(1).join(" ").trim();
3662
4342
  if (!name) return null;
3663
4343
  return {
3664
- type: matched[1],
4344
+ type,
3665
4345
  name
3666
4346
  };
3667
4347
  }
@@ -3669,68 +4349,70 @@ function typeLabel(type) {
3669
4349
  return type === "plugin" ? "插件" : "服务";
3670
4350
  }
3671
4351
  function registerInstallCommands(ctx) {
3672
- const dispose = ctx.handle("message", async (event) => {
3673
- const text$1 = ctx.text(event)?.trim();
3674
- if (!text$1 || event?.user_id === event?.self_id) return;
3675
- const prefix = getCommandPrefix();
3676
- const installHead = `${prefix}install`;
3677
- const uninstallHead = `${prefix}uninstall`;
3678
- if (!text$1.startsWith(installHead) && !text$1.startsWith(uninstallHead)) return;
3679
- if (!isEventOwner(event)) {
3680
- ctx.logger.warn("[core] install/uninstall 指令仅主人可用");
3681
- return;
3682
- }
3683
- if (text$1.startsWith(installHead)) {
3684
- const parsed$1 = parseTargetArgs(text$1, installHead);
3685
- if (!parsed$1) {
3686
- await replyText(event, `用法:${prefix}install plugin <名称> 或 ${prefix}install service <名称>`);
4352
+ const register = (name) => ctx.command({
4353
+ name,
4354
+ aliases: name === "install" ? ["安装"] : ["卸载"],
4355
+ permission: "master",
4356
+ priority: -1e3,
4357
+ description: name === "install" ? "从 npm 安装插件/服务" : "卸载插件/服务",
4358
+ handler: async ({ event, args }) => {
4359
+ if (name === "install") {
4360
+ const parsed$1 = parseTargetArgs(args);
4361
+ if (!parsed$1) {
4362
+ await replyText(event, `用法:${ctx.config.prefix ?? "."}install plugin <名称> 或 ${ctx.config.prefix ?? "."}install service <名称>`);
4363
+ return;
4364
+ }
4365
+ await replyText(event, `正在从 npm 安装${typeLabel(parsed$1.type)} ${parsed$1.name}...`);
4366
+ try {
4367
+ const result = await installPackage(parsed$1.type, parsed$1.name);
4368
+ if (!result.ok) {
4369
+ ctx.logger.error(`[core] 安装失败: ${result.error || result.output}`);
4370
+ await replyText(event, `安装失败:${result.error || result.output}`);
4371
+ return;
4372
+ }
4373
+ const lines = [
4374
+ `已安装 ${result.packageName}`,
4375
+ result.enabled ? "已在 plugins 中启用" : "",
4376
+ "重启后生效"
4377
+ ].filter(Boolean);
4378
+ await replyText(event, lines.join("\n"));
4379
+ } catch (error) {
4380
+ ctx.logger.error(`[core] 安装失败: ${error}`);
4381
+ await replyText(event, `安装失败:${String(error)}`);
4382
+ }
4383
+ return;
4384
+ }
4385
+ const parsed = parseTargetArgs(args);
4386
+ if (!parsed) {
4387
+ await replyText(event, `用法:${ctx.config.prefix ?? "."}uninstall plugin <名称> 或 ${ctx.config.prefix ?? "."}uninstall service <名称>`);
3687
4388
  return;
3688
4389
  }
3689
- await replyText(event, `正在从 npm 安装${typeLabel(parsed$1.type)} ${parsed$1.name}...`);
4390
+ await replyText(event, `正在卸载${typeLabel(parsed.type)} ${parsed.name}...`);
3690
4391
  try {
3691
- const result = await installPackage(parsed$1.type, parsed$1.name);
4392
+ const result = await uninstallPackage(parsed.type, parsed.name);
3692
4393
  if (!result.ok) {
3693
- ctx.logger.error(`[core] 安装失败: ${result.error || result.output}`);
3694
- await replyText(event, `安装失败:${result.error || result.output}`);
4394
+ ctx.logger.error(`[core] 卸载失败: ${result.error || result.output}`);
4395
+ await replyText(event, `卸载失败:${result.error || result.output}`);
3695
4396
  return;
3696
4397
  }
3697
4398
  const lines = [
3698
- `已安装 ${result.packageName}`,
3699
- result.enabled ? "已在 plugins 中启用" : "",
4399
+ `已卸载 ${result.packageName}`,
4400
+ result.removedFromConfig ? "已从 plugins 中移除" : "",
3700
4401
  "重启后生效"
3701
4402
  ].filter(Boolean);
3702
4403
  await replyText(event, lines.join("\n"));
3703
4404
  } catch (error) {
3704
- ctx.logger.error(`[core] 安装失败: ${error}`);
3705
- await replyText(event, `安装失败:${String(error)}`);
4405
+ ctx.logger.error(`[core] 卸载失败: ${error}`);
4406
+ await replyText(event, `卸载失败:${String(error)}`);
3706
4407
  }
3707
- return;
3708
- }
3709
- const parsed = parseTargetArgs(text$1, uninstallHead);
3710
- if (!parsed) {
3711
- await replyText(event, `用法:${prefix}uninstall plugin <名称> 或 ${prefix}uninstall service <名称>`);
3712
- return;
3713
- }
3714
- await replyText(event, `正在卸载${typeLabel(parsed.type)} ${parsed.name}...`);
3715
- try {
3716
- const result = await uninstallPackage(parsed.type, parsed.name);
3717
- if (!result.ok) {
3718
- ctx.logger.error(`[core] 卸载失败: ${result.error || result.output}`);
3719
- await replyText(event, `卸载失败:${result.error || result.output}`);
3720
- return;
3721
- }
3722
- const lines = [
3723
- `已卸载 ${result.packageName}`,
3724
- result.removedFromConfig ? "已从 plugins 中移除" : "",
3725
- "重启后生效"
3726
- ].filter(Boolean);
3727
- await replyText(event, lines.join("\n"));
3728
- } catch (error) {
3729
- ctx.logger.error(`[core] 卸载失败: ${error}`);
3730
- await replyText(event, `卸载失败:${String(error)}`);
3731
4408
  }
3732
4409
  });
3733
- return dispose;
4410
+ const install = register("install");
4411
+ const uninstall = register("uninstall");
4412
+ return () => {
4413
+ install();
4414
+ uninstall();
4415
+ };
3734
4416
  }
3735
4417
 
3736
4418
  //#endregion
@@ -3744,64 +4426,76 @@ function renderMarketText(kind, items) {
3744
4426
  return [`Mioku ${label}市场(共 ${items.length} 个)`, ...lines].join("\n");
3745
4427
  }
3746
4428
  function registerMarketCommands(ctx) {
3747
- const dispose = ctx.handle("message", async (event) => {
3748
- const text$1 = ctx.text(event)?.trim();
3749
- if (!text$1 || event?.user_id === event?.self_id) return;
3750
- const prefix = getCommandPrefix();
3751
- const kind = text$1 === `${prefix}plugin-market` || text$1 === `${prefix}插件市场` ? "plugin" : text$1 === `${prefix}service-market` || text$1 === `${prefix}服务市场` ? "service" : null;
3752
- if (!kind) return;
3753
- if (!isEventOwner(event)) {
3754
- ctx.logger.warn("[core] market 指令仅主人可用");
3755
- return;
3756
- }
3757
- let items;
3758
- try {
3759
- items = await getMarketItems(kind);
3760
- } catch (error) {
3761
- ctx.logger.error(`[core] 获取市场信息失败: ${error}`);
3762
- await replyText(event, `获取市场失败:${String(error)}`);
3763
- return;
3764
- }
3765
- await sendTextOrForward({
3766
- ctx,
3767
- event,
3768
- text: renderMarketText(kind, items),
3769
- source: `Mioku ${kind === "plugin" ? "插件" : "服务"}市场`,
3770
- summary: `共 ${items.length} 个`
3771
- });
4429
+ const pluginMarket = ctx.command({
4430
+ id: "market",
4431
+ name: "plugin-market",
4432
+ aliases: ["插件市场"],
4433
+ permission: "master",
4434
+ priority: -1e3,
4435
+ description: "查看插件市场",
4436
+ handler: (input) => handleMarket(ctx, input.event, "plugin")
4437
+ });
4438
+ const serviceMarket = ctx.command({
4439
+ id: "market",
4440
+ name: "service-market",
4441
+ aliases: ["服务市场"],
4442
+ permission: "master",
4443
+ priority: -1e3,
4444
+ description: "查看服务市场",
4445
+ handler: (input) => handleMarket(ctx, input.event, "service")
4446
+ });
4447
+ return () => {
4448
+ pluginMarket();
4449
+ serviceMarket();
4450
+ };
4451
+ }
4452
+ async function handleMarket(ctx, event, kind) {
4453
+ let items;
4454
+ try {
4455
+ items = await getMarketItems(kind);
4456
+ } catch (error) {
4457
+ ctx.logger.error(`[core] 获取市场信息失败: ${error}`);
4458
+ await replyText(event, `获取市场失败:${String(error)}`);
4459
+ return;
4460
+ }
4461
+ await sendTextOrForward({
4462
+ ctx,
4463
+ event,
4464
+ text: renderMarketText(kind, items),
4465
+ source: `Mioku ${kind === "plugin" ? "插件" : "服务"}市场`,
4466
+ summary: `共 ${items.length} 个`
3772
4467
  });
3773
- return dispose;
3774
4468
  }
3775
4469
 
3776
4470
  //#endregion
3777
4471
  //#region src/builtin/core/commands/restart.ts
3778
4472
  function registerRestartCommand(ctx) {
3779
- const dispose = ctx.handle("message", async (event) => {
3780
- const text$1 = ctx.text(event)?.trim();
3781
- if (!text$1 || event?.user_id === event?.self_id) return;
3782
- const prefix = getCommandPrefix();
3783
- if (text$1 !== `${prefix}restart` && text$1 !== `${prefix}重启`) return;
3784
- if (!isEventOwner(event)) {
3785
- ctx.logger.warn("[core] restart 指令仅主人可用");
3786
- return;
3787
- }
3788
- const uptimeMs = process.uptime() * 1e3;
3789
- const selfId = Number(event?.self_id || 0);
3790
- const groupId = event?.message_type === "group" && event?.group_id ? Number(event.group_id) : null;
3791
- const userId = Number(event?.user_id || 0);
3792
- const marker = {
3793
- initiatedAt: Date.now(),
3794
- selfId,
3795
- groupId,
3796
- userId
3797
- };
3798
- try {
3799
- await replyText(event, `Bot已运行${formatUptime(uptimeMs)},正在重启...`);
3800
- } catch (error) {
3801
- ctx.logger.warn(`[core] 发送重启提示失败: ${error}`);
4473
+ const dispose = ctx.command({
4474
+ name: "restart",
4475
+ aliases: ["重启"],
4476
+ permission: "master",
4477
+ priority: -1e3,
4478
+ description: "重启机器人进程",
4479
+ handler: async ({ event }) => {
4480
+ if (event?.user_id === event?.self_id) return;
4481
+ const uptimeMs = process.uptime() * 1e3;
4482
+ const selfId = Number(event?.self_id || 0);
4483
+ const groupId = event?.message_type === "group" && event?.group_id ? Number(event.group_id) : null;
4484
+ const userId = Number(event?.user_id || 0);
4485
+ const marker = {
4486
+ initiatedAt: Date.now(),
4487
+ selfId,
4488
+ groupId,
4489
+ userId
4490
+ };
4491
+ try {
4492
+ await replyText(event, `Bot已运行${formatUptime(uptimeMs)},正在重启...`);
4493
+ } catch (error) {
4494
+ ctx.logger.warn(`[core] 发送重启提示失败: ${error}`);
4495
+ }
4496
+ ctx.logger.info(`[core] 正在执行重启命令 ${formatUptime(uptimeMs)}`);
4497
+ triggerRestart(marker);
3802
4498
  }
3803
- ctx.logger.info(`[core] 正在执行重启命令 ${formatUptime(uptimeMs)}`);
3804
- triggerRestart(marker);
3805
4499
  });
3806
4500
  return dispose;
3807
4501
  }
@@ -3839,63 +4533,63 @@ function chunk(arr, size) {
3839
4533
  return result;
3840
4534
  }
3841
4535
  function registerLogCommand(ctx) {
3842
- const dispose = ctx.handle("message", async (event) => {
3843
- const text$1 = ctx.text(event)?.trim();
3844
- if (!text$1 || event?.user_id === event?.self_id) return;
3845
- const prefix = getCommandPrefix();
3846
- if (text$1 !== `${prefix}log` && text$1 !== `${prefix}日志`) return;
3847
- if (!isEventOwner(event)) {
3848
- ctx.logger.warn("[core] 日志指令仅主人可用");
3849
- return;
3850
- }
3851
- const selfId = String(event?.self_id || "");
3852
- const bot = event.bot;
3853
- if (!bot) return;
3854
- const logFile = getActiveLogFile();
3855
- if (!logFile) {
3856
- await replyText(event, "未找到日志文件。");
3857
- return;
3858
- }
3859
- let lines;
3860
- try {
3861
- lines = readLastLines(logFile, LOG_LINE_COUNT);
3862
- } catch (error) {
3863
- ctx.logger.error(`[core] 读取日志失败: ${error}`);
3864
- await replyText(event, `读取日志失败:${String(error)}`);
3865
- return;
3866
- }
3867
- if (lines.length === 0) {
3868
- await replyText(event, "日志为空。");
3869
- return;
3870
- }
3871
- let infoCount = 0;
3872
- let warnCount = 0;
3873
- let errorCount = 0;
3874
- for (const line of lines) if (/\bERROR\b|\berror\b/i.test(line)) errorCount++;
3875
- else if (/\bWARN\b|\bwarn\b/i.test(line)) warnCount++;
3876
- else if (/\bINFO\b|\binfo\b/i.test(line)) infoCount++;
3877
- const summary = `info ${infoCount} warn ${warnCount} error ${errorCount}`;
3878
- const chunks = chunk(lines, LINES_PER_NODE);
3879
- const nodes = chunks.map((chunkLines, idx) => ({
3880
- user_id: selfId,
3881
- nickname: `第${idx + 1}条日志`,
3882
- content: [ctx.segment.text(chunkLines.join("\n"))]
3883
- }));
3884
- try {
3885
- const target = event?.message_type === "group" && event?.group_id ? {
3886
- type: "group",
3887
- group_id: event.group_id
3888
- } : event?.user_id ? {
3889
- type: "private",
3890
- user_id: event.user_id
3891
- } : void 0;
3892
- if (target) await bot.sendForward(target, nodes, {
3893
- source: "最近100条运行日志",
3894
- summary
3895
- });
3896
- } catch (error) {
3897
- ctx.logger.error(`[core] 发送日志转发失败: ${error}`);
3898
- await replyText(event, `发送日志失败:${String(error)}`);
4536
+ const dispose = ctx.command({
4537
+ name: "log",
4538
+ aliases: ["日志"],
4539
+ permission: "master",
4540
+ priority: -1e3,
4541
+ description: "查看最近100条日志",
4542
+ handler: async ({ event }) => {
4543
+ if (event?.user_id === event?.self_id) return;
4544
+ const selfId = String(event?.self_id || "");
4545
+ const bot = event.bot;
4546
+ if (!bot) return;
4547
+ const logFile = getActiveLogFile();
4548
+ if (!logFile) {
4549
+ await replyText(event, "未找到日志文件。");
4550
+ return;
4551
+ }
4552
+ let lines;
4553
+ try {
4554
+ lines = readLastLines(logFile, LOG_LINE_COUNT);
4555
+ } catch (error) {
4556
+ ctx.logger.error(`[core] 读取日志失败: ${error}`);
4557
+ await replyText(event, `读取日志失败:${String(error)}`);
4558
+ return;
4559
+ }
4560
+ if (lines.length === 0) {
4561
+ await replyText(event, "日志为空。");
4562
+ return;
4563
+ }
4564
+ let infoCount = 0;
4565
+ let warnCount = 0;
4566
+ let errorCount = 0;
4567
+ for (const line of lines) if (/\bERROR\b|\berror\b/i.test(line)) errorCount++;
4568
+ else if (/\bWARN\b|\bwarn\b/i.test(line)) warnCount++;
4569
+ else if (/\bINFO\b|\binfo\b/i.test(line)) infoCount++;
4570
+ const summary = `info ${infoCount} warn ${warnCount} error ${errorCount}`;
4571
+ const chunks = chunk(lines, LINES_PER_NODE);
4572
+ const nodes = chunks.map((chunkLines, idx) => ({
4573
+ user_id: selfId,
4574
+ nickname: `第${idx + 1}条日志`,
4575
+ content: [ctx.segment.text(chunkLines.join("\n"))]
4576
+ }));
4577
+ try {
4578
+ const target = event?.message_type === "group" && event?.group_id ? {
4579
+ type: "group",
4580
+ group_id: event.group_id
4581
+ } : event?.user_id ? {
4582
+ type: "private",
4583
+ user_id: event.user_id
4584
+ } : void 0;
4585
+ if (target) await bot.sendForward(target, nodes, {
4586
+ source: "最近100条运行日志",
4587
+ summary
4588
+ });
4589
+ } catch (error) {
4590
+ ctx.logger.error(`[core] 发送日志转发失败: ${error}`);
4591
+ await replyText(event, `发送日志失败:${String(error)}`);
4592
+ }
3899
4593
  }
3900
4594
  });
3901
4595
  return dispose;
@@ -4122,7 +4816,6 @@ const core = definePlugin({
4122
4816
  description: "mioku 内置核心插件",
4123
4817
  async setup(ctx) {
4124
4818
  const rawPrefix = ctx.config.prefix ?? ".";
4125
- const cmdPrefix = new RegExp(`^${escapeRegExp(rawPrefix)}`);
4126
4819
  const displayPrefix = rawPrefix;
4127
4820
  const statusAdminOnly = ctx.config.status_permission === "admin-only";
4128
4821
  rootLogger.info("========================================");
@@ -4140,12 +4833,17 @@ const core = definePlugin({
4140
4833
  }));
4141
4834
  } else ctx.logger.warn("config-service 未加载,core 插件将使用默认配置");
4142
4835
  let accessRules = ensureAccessControlConfig();
4836
+ ctx.commands.setAccessControl(accessRules);
4143
4837
  if (configService) {
4144
4838
  await configService.registerConfig("core", "access-control", accessRules);
4145
4839
  const persisted = await configService.getConfig("core", "access-control");
4146
- if (persisted) accessRules = normalizeAccessConfig(persisted);
4840
+ if (persisted) {
4841
+ accessRules = normalizeAccessConfig(persisted);
4842
+ ctx.commands.setAccessControl(accessRules);
4843
+ }
4147
4844
  disposers.push(configService.onConfigChange("core", "access-control", (next) => {
4148
4845
  accessRules = normalizeAccessConfig(next);
4846
+ ctx.commands.setAccessControl(accessRules);
4149
4847
  }));
4150
4848
  }
4151
4849
  rootLogger.info(`访问控制已挂载: ${ctx.bots.length} 个 bot`);
@@ -4207,17 +4905,49 @@ const core = definePlugin({
4207
4905
  bots: collectBots(),
4208
4906
  adapters: collectAdapters()
4209
4907
  }));
4908
+ ctx.command({
4909
+ name: "help",
4910
+ aliases: ["帮助"],
4911
+ permission: "master",
4912
+ priority: -1e3,
4913
+ description: "显示帮助信息",
4914
+ handler: async ({ event }) => {
4915
+ await event.reply(buildCoreHelpText(displayPrefix));
4916
+ }
4917
+ });
4918
+ ctx.command({
4919
+ name: "status",
4920
+ aliases: ["状态"],
4921
+ priority: -1e3,
4922
+ description: "显示框架状态",
4923
+ handler: async ({ event }) => {
4924
+ if (statusAdminOnly && !isEventOwnerOrAdmin(event)) return;
4925
+ await event.reply(await formatMiokuStatus(await getStatus()));
4926
+ }
4927
+ });
4928
+ ctx.command({
4929
+ name: "adapter",
4930
+ aliases: ["适配器"],
4931
+ priority: -1e3,
4932
+ description: "显示适配器与连接实例",
4933
+ handler: async ({ event }) => {
4934
+ if (statusAdminOnly && !isEventOwnerOrAdmin(event)) return;
4935
+ await event.reply(await getAdapterText());
4936
+ }
4937
+ });
4210
4938
  const atTarget = (event) => {
4211
4939
  const at = event.message.find((seg) => seg.type === "at");
4212
4940
  const qq = at?.data?.qq ?? at?.data?.target;
4213
4941
  return qq != null ? String(qq) : void 0;
4214
4942
  };
4215
- disposers.push(ctx.handle("message", async (event) => {
4943
+ const handleCoreMessage = async (event) => {
4216
4944
  const ev = event;
4217
4945
  const text$1 = ev.message.text();
4218
- if (!cmdPrefix.test(text$1)) return;
4946
+ const currentPrefix = String(ctx.config.prefix ?? ".");
4947
+ const currentCmdPrefix = new RegExp(`^${escapeRegExp(currentPrefix)}`);
4948
+ if (!currentCmdPrefix.test(text$1)) return;
4219
4949
  if (statusAdminOnly && !isEventOwnerOrAdmin(ev)) return;
4220
- const bare = text$1.replace(cmdPrefix, "").trim();
4950
+ const bare = text$1.replace(currentCmdPrefix, "").trim();
4221
4951
  if (bare === "状态" || bare === "status") {
4222
4952
  await ev.reply(await formatMiokuStatus(await getStatus()));
4223
4953
  return;
@@ -4226,10 +4956,10 @@ const core = definePlugin({
4226
4956
  await ev.reply(await getAdapterText());
4227
4957
  return;
4228
4958
  }
4229
- if (!isEventOwner(ev)) return;
4959
+ if (!isEventMaster(ev)) return;
4230
4960
  const { cmd, params } = createCmd(text$1);
4231
4961
  if (!cmd) return;
4232
- const subCmd = cmd.replace(cmdPrefix, "").replace(/\s+/g, "");
4962
+ const subCmd = cmd.replace(currentCmdPrefix, "").replace(/\s+/g, "");
4233
4963
  const target = params[0];
4234
4964
  switch (subCmd) {
4235
4965
  case "help":
@@ -4451,7 +5181,29 @@ const core = definePlugin({
4451
5181
  process.exit(0);
4452
5182
  }
4453
5183
  }
4454
- }));
5184
+ };
5185
+ for (const command of [
5186
+ {
5187
+ name: "plugin",
5188
+ aliases: ["插件"],
5189
+ description: "插件管理"
5190
+ },
5191
+ {
5192
+ name: "settings",
5193
+ aliases: ["设置"],
5194
+ description: "框架设置管理"
5195
+ },
5196
+ {
5197
+ name: "exit",
5198
+ aliases: ["退出"],
5199
+ description: "退出框架进程"
5200
+ }
5201
+ ]) ctx.command({
5202
+ ...command,
5203
+ permission: "master",
5204
+ priority: -1e3,
5205
+ handler: ({ event }) => handleCoreMessage(event)
5206
+ });
4455
5207
  rootLogger.info("========================================");
4456
5208
  rootLogger.info(" Mioku 服务初始化完成");
4457
5209
  rootLogger.info("========================================");
@@ -4858,7 +5610,8 @@ var MiokuRuntime = class {
4858
5610
  #enabledPlugins = new Map();
4859
5611
  #driverFactory;
4860
5612
  #builtinPlugins;
4861
- #dedupEnabled;
5613
+ #correlator;
5614
+ #commands;
4862
5615
  #started = false;
4863
5616
  #stopped = false;
4864
5617
  constructor(options) {
@@ -4866,7 +5619,7 @@ var MiokuRuntime = class {
4866
5619
  this.#logger = options.logger;
4867
5620
  this.#driverFactory = options.driverFactory ?? (() => createDefaultDriver());
4868
5621
  this.#builtinPlugins = options.builtinPlugins ?? BUILTIN_PLUGINS$1;
4869
- this.#dedupEnabled = options.dedup?.crossAdapter !== false;
5622
+ this.#correlator = options.dedup?.crossAdapter === false ? void 0 : new EventCorrelator({ logger: this.#logger.child({ scope: "dedup" }) });
4870
5623
  this.#driver = this.#driverFactory();
4871
5624
  this.#bus = new EventBus();
4872
5625
  this.#bus.setLogger((level, message, detail) => {
@@ -4876,6 +5629,14 @@ var MiokuRuntime = class {
4876
5629
  });
4877
5630
  this.#bots = new BotRegistry();
4878
5631
  this.#capabilities = new CapabilityRegistry();
5632
+ this.#commands = new CommandManager({
5633
+ getDefaultPrefix: () => String(botConfig.prefix ?? "."),
5634
+ getOwners: () => botConfig.owners,
5635
+ getAdmins: () => botConfig.admins,
5636
+ logger: this.#logger
5637
+ });
5638
+ this.#bus.setFilter((registration, event) => this.#commands.shouldDispatch(registration.source, event));
5639
+ setActiveCommandManager(this.#commands);
4879
5640
  }
4880
5641
  get cwd() {
4881
5642
  return this.#cwd;
@@ -4889,6 +5650,13 @@ var MiokuRuntime = class {
4889
5650
  get bus() {
4890
5651
  return this.#bus;
4891
5652
  }
5653
+ get commands() {
5654
+ return this.#commands;
5655
+ }
5656
+ /** 事件关联器(跨适配器去重);为 undefined 时表示已禁用 */
5657
+ get correlator() {
5658
+ return this.#correlator;
5659
+ }
4892
5660
  get bots() {
4893
5661
  return this.#bots.all();
4894
5662
  }
@@ -4903,14 +5671,16 @@ var MiokuRuntime = class {
4903
5671
  return this.#bots.pick(bot_id);
4904
5672
  }
4905
5673
  listPlugins() {
4906
- return Array.from(this.#enabledPlugins.entries()).map(([key, entry]) => {
5674
+ return Array.from(this.#enabledPlugins.entries()).map(([key]) => {
4907
5675
  const colon = key.indexOf(":");
4908
5676
  const type = key.slice(0, colon);
4909
5677
  const name = key.slice(colon + 1);
5678
+ const isExternal = type !== "builtin";
5679
+ const version$2 = isExternal ? getPluginMetadata(name)?.version : void 0;
4910
5680
  return {
4911
5681
  name,
4912
5682
  type: type === "builtin" ? "builtin" : "external",
4913
- version: entry.plugin.version
5683
+ version: version$2
4914
5684
  };
4915
5685
  });
4916
5686
  }
@@ -4996,13 +5766,17 @@ var MiokuRuntime = class {
4996
5766
  driver: this.#driver,
4997
5767
  capabilities: this.#capabilities,
4998
5768
  logger: this.#logger.child({ adapter: state.definition.name }),
4999
- emit: (event) => this.#emitLifecycle(event)
5769
+ emit: (event) => this.#emitLifecycle(event),
5770
+ correlator: this.#correlator,
5771
+ commands: this.#commands
5000
5772
  });
5001
5773
  }
5002
5774
  async #loadPlugin(plugin, type) {
5003
5775
  const cleanupTasks = [];
5004
5776
  const ctx = new MiokuContext({
5005
5777
  pluginName: `${type}:${plugin.name}`,
5778
+ pluginId: plugin.name === "mioku-core" ? "core" : plugin.name,
5779
+ commands: this.#commands,
5006
5780
  bus: this.#bus,
5007
5781
  bots: this.#bots,
5008
5782
  driver: this.#driver,
@@ -5010,7 +5784,7 @@ var MiokuRuntime = class {
5010
5784
  config: botConfig,
5011
5785
  logger: this.#logger.child({ plugin: plugin.name }),
5012
5786
  priority: plugin.priority ?? 100,
5013
- dedup: this.#dedupEnabled,
5787
+ correlator: this.#correlator,
5014
5788
  getAdapter: (name) => this.getAdapter(name),
5015
5789
  listAdapters: () => this.adapters,
5016
5790
  onUpdateConfig: async (updater) => {
@@ -5299,6 +6073,9 @@ var MiokuRuntime = class {
5299
6073
  this.#enabledPlugins.clear();
5300
6074
  this.#capabilities.clear();
5301
6075
  this.#bots.clear();
6076
+ this.#correlator?.clear();
6077
+ this.#commands.clear();
6078
+ setActiveCommandManager(void 0);
5302
6079
  resetPluginMetadata();
5303
6080
  try {
5304
6081
  await this.#driver.shutdown();
@@ -5315,22 +6092,38 @@ const createRuntime = (options) => {
5315
6092
 
5316
6093
  //#endregion
5317
6094
  //#region src/runtime/plugin-artifacts.ts
6095
+ const commandKey = (value) => String(value).trim().replace(/^[#/.]+\s*/, "").split(/\s+/)[0].toLowerCase();
5318
6096
  async function registerPluginArtifacts(ctx) {
5319
6097
  const helpService = ctx ? getService(ctx, Services.Help) : servicesRegistry["help"];
5320
6098
  const plugins = getPluginMetadataList();
6099
+ const commandManager = getActiveCommandManager();
6100
+ const metadataByName = new Map(plugins.map((meta) => [meta.name, meta]));
6101
+ const pluginNames = new Set([...metadataByName.keys(), ...(commandManager?.catalog() ?? []).filter((item) => item.kind === "plugin").map((item) => item.plugin)]);
5321
6102
  let helpCount = 0;
5322
- for (const meta of plugins) {
5323
- if (meta.config?.help) if (helpService) {
5324
- helpService.registerHelp(meta.name, meta.config.help);
6103
+ for (const pluginName of pluginNames) {
6104
+ const meta = metadataByName.get(pluginName);
6105
+ const generated = commandManager?.getPluginHelp(pluginName) ?? meta?.config?.help;
6106
+ const existing = helpService?.getHelp(pluginName);
6107
+ const help = generated ? {
6108
+ ...generated,
6109
+ title: existing?.title || generated.title,
6110
+ description: existing?.description || generated.description,
6111
+ commands: [...existing?.commands ?? [], ...generated.commands.filter((command) => {
6112
+ const key = commandKey(command.cmd);
6113
+ return !(existing?.commands ?? []).some((item) => commandKey(item.cmd) === key);
6114
+ })]
6115
+ } : existing;
6116
+ if (help) if (helpService) {
6117
+ helpService.registerHelp(pluginName, help);
5325
6118
  helpCount++;
5326
- } else rootLogger.warn(`[plugin-artifacts] 帮助服务未加载,跳过插件 "${meta.name}" 的帮助注册`);
5327
- const required = meta.config?.services;
6119
+ } else rootLogger.warn(`[plugin-artifacts] 帮助服务未加载,跳过插件 "${pluginName}" 的帮助注册`);
6120
+ const required = meta?.config?.services;
5328
6121
  if (required && required.length > 0) {
5329
6122
  const missing = required.filter((name) => !servicesRegistry[name]);
5330
- if (missing.length > 0) rootLogger.warn(`插件 "${meta.name}" 声明依赖服务 [${missing.join(", ")}],但它们未加载`);
6123
+ if (missing.length > 0) rootLogger.warn(`插件 "${pluginName}" 声明依赖服务 [${missing.join(", ")}],但它们未加载`);
5331
6124
  }
5332
6125
  }
5333
- rootLogger.info(`[plugin-artifacts] 已处理 ${plugins.length} 个插件的元数据,注册帮助 ${helpCount} 个`);
6126
+ rootLogger.info(`[plugin-artifacts] 已处理 ${pluginNames.size} 个插件,注册帮助 ${helpCount} 个`);
5334
6127
  }
5335
6128
 
5336
6129
  //#endregion
@@ -5461,6 +6254,7 @@ async function start(options = {}) {
5461
6254
  };
5462
6255
  process.on("SIGINT", () => void shutdown("SIGINT"));
5463
6256
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
6257
+ installProcessErrorGuards();
5464
6258
  return { stop: (reason) => stopRuntime(reason) };
5465
6259
  }
5466
6260
  function readVersion() {
@@ -5473,7 +6267,16 @@ function readVersion() {
5473
6267
  }
5474
6268
  }
5475
6269
  const version$1 = readVersion();
6270
+ function installProcessErrorGuards() {
6271
+ if (process.listenerCount("unhandledRejection") === 0) process.on("unhandledRejection", (reason) => {
6272
+ const detail = reason instanceof Error ? reason.stack ?? reason.message : reason;
6273
+ rootLogger.error(`[unhandledRejection] ${String(detail)}`);
6274
+ });
6275
+ if (process.listenerCount("uncaughtException") === 0) process.on("uncaughtException", (err) => {
6276
+ rootLogger.error(`[uncaughtException] ${err.stack ?? err.message ?? String(err)}`);
6277
+ });
6278
+ }
5476
6279
 
5477
6280
  //#endregion
5478
- export { AI_GEMINI_THINKING_LEVELS, AI_THINKING_LEVELS, AdapterContextImpl, AdapterRegistrationConflictError, ArchMap, BOT_CWD, BUILTIN_PLUGINS, BotRegistry, CORE_PLUGINS, CapabilityRegistry, ChromeUA, DriverShutdownError, EventBus, HttpRequestError, MessageSegmentImpl, MiokuContext, MiokuRuntime, ServiceManager, Services, SystemMap, TOOL_RESULT_FOLLOWUP_KEY, UnsupportedCapabilityError, WebSocketConnectTimeoutError, addService, applyLogLevel, asMessage, atOf, avatarGet, avatarSet, base64Decode, base64Encode, bindCapabilities, botConfig, botStatus, box, buildAdapterReport, buildMiokuStatus, buildRoutes, buildSpawnPlan, clamp, collectBotStats, colorize, colors, commandExists, connectedBotKey, connectedBots, conversationGetHistory, createCmd, createDB, createDefaultDriver, createFriendRef, createGroupRef, createImportContext, createMessage, createMiokuLogger, createRuntime, createStore, dayjs, dedent, defineAdapter, defineCapability, definePlugin, defineService, defineState, deleteServiceConfig, discoverAdapterCandidates, discoverPluginCandidates, displayAdapterId, ensureDataDir, filesize, filter, find, findLocalPlugins, formatAdapterReport, formatDuration, formatMiokuStatus, formatMiokuStatusFn, forwardSend, friendDelete, friendGetInfo, friendGetList, fs, getAllPluginRuntimeStates, getBuiltinPlugins, getConfigDir, getDataDir, getGTk, getPluginConfigDir, getPluginDataDir, getPluginMetadata, getPluginMetadataList, getPluginRuntimeState, getService, getServiceConfig, getServiceConfigDir, getServiceConfigs, getServiceDataDir, getTerminalInput, groupGetInfo, groupGetList, groupGetMembers, groupLeave, groupSetName, groupSetPortrait, groupSetWholeBan, hasEventRight, hasPluginState, hasRight, hasService, isAdapterEvent, isAdmin, isBoolean, isBotEvent, isDefined, isEventAdmin, isEventOwner, isEventOwnerOrAdmin, isFunction, isMessage, isMessageEvent, isMetaEvent, isMiokuPlugin, isNoticeEvent, isNumber, isObject, isOwner, isOwnerOrAdmin, isRequestEvent, isSegment, isString, loadAdapterDefinition, loadLocalPlugin, loadNpmPlugin, localNum, localeDate, localeTime, rootLogger as logger, match, md5, memberBan, memberGetInfo, memberKick, memberPoke, memberSetAdmin, memberSetCard, memberSetTitle, messageGet, messageGetForward, messageRecall, messageSend, mriLib as mri, noNullish, normalizeAIThinkingLevel, normalizeAdmins, normalizeOwners, normalizePlugins, normalizeSkillPermissionRole, noticeAdmins, noticeFriends, noticeGroups, noticeOwners, path, prettyMs, profileSet, qs, randomId, randomInt, randomItem, randomItems, readMiokuConfig, readPackageJson, readPackageJsonSafe, registerPluginArtifacts, registerServiceConfig, registerStatusProvider, reloadMiokuConfig, removePluginMetadata, requireService, resetPluginMetadata, resetPluginRuntimeState, resolveCommand, resolveEntry, resolveLocalPluginEntry, resolveLocalPluginPath, rootLogger, runCommand, runCommandInherit, segment, sendMessage, manager_default as serviceManager, servicesRegistry, setBotCwd, setBuiltinPlugins, setPluginMetadata, setPluginRuntimeState, setService, setWritableConfig, start, startRuntime, string2argv, stringifyError, stripAnsi, text, toArray, toId, toUserId, unique, updateMiokuConfig, updateServiceConfig, uuid, version$1 as version, wait, writePackageJson };
6281
+ export { AI_GEMINI_THINKING_LEVELS, AI_THINKING_LEVELS, AdapterContextImpl, AdapterRegistrationConflictError, ArchMap, BOT_CWD, BUILTIN_PLUGINS, BotRegistry, CORE_PLUGINS, CapabilityRegistry, ChromeUA, CommandManager, DriverShutdownError, EventBus, EventCorrelator, HttpRequestError, MessageSegmentImpl, MiokuContext, MiokuRuntime, ServiceManager, Services, SystemMap, TOOL_RESULT_FOLLOWUP_KEY, UnsupportedCapabilityError, WebSocketConnectTimeoutError, addService, applyLogLevel, asMessage, atOf, avatarGet, avatarSet, base64Decode, base64Encode, bindCapabilities, botConfig, botStatus, box, buildAdapterReport, buildMiokuStatus, buildRoutes, buildSpawnPlan, clamp, collectBotStats, colorize, colors, commandExists, connectedBotKey, connectedBots, conversationGetHistory, createCmd, createDB, createDefaultDriver, createFriendRef, createGroupRef, createImportContext, createMessage, createMiokuLogger, createRuntime, createStore, dayjs, dedent, defineAdapter, defineCapability, definePlugin, defineService, defineState, deleteServiceConfig, discoverAdapterCandidates, discoverPluginCandidates, displayAdapterId, ensureDataDir, filesize, filter, find, findLocalPlugins, formatAdapterReport, formatDuration, formatMiokuStatus, formatMiokuStatusFn, forwardSend, friendDelete, friendGetInfo, friendGetList, fs, getActiveCommandManager, getAllPluginRuntimeStates, getBuiltinPlugins, getConfigDir, getDataDir, getGTk, getPluginConfigDir, getPluginDataDir, getPluginMetadata, getPluginMetadataList, getPluginRuntimeState, getService, getServiceConfig, getServiceConfigDir, getServiceConfigs, getServiceDataDir, getTerminalInput, groupGetInfo, groupGetList, groupGetMembers, groupLeave, groupSetName, groupSetPortrait, groupSetWholeBan, hasEventRight, hasPluginState, hasRight, hasService, isAdapterEvent, isAdmin, isBoolean, isBotEvent, isDefined, isEventAdmin, isEventAdminConfigOnly, isEventGroupAdmin, isEventGroupOwner, isEventMaster, isEventOwner, isEventOwnerOrAdmin, isFunction, isMessage, isMessageEvent, isMetaEvent, isMiokuPlugin, isNoticeEvent, isNumber, isObject, isOwner, isOwnerOrAdmin, isRequestEvent, isSegment, isString, loadAdapterDefinition, loadLocalPlugin, loadNpmPlugin, localNum, localeDate, localeTime, rootLogger as logger, match, md5, memberBan, memberGetInfo, memberKick, memberPoke, memberSetAdmin, memberSetCard, memberSetTitle, messageGet, messageGetForward, messageRecall, messageSend, mriLib as mri, noNullish, normalizeAIThinkingLevel, normalizeAdmins, normalizeOwners, normalizePlugins, normalizeSkillPermissionRole, noticeAdmins, noticeFriends, noticeGroups, noticeOwners, path, prettyMs, profileSet, qs, randomId, randomInt, randomItem, randomItems, readMiokuConfig, readPackageJson, readPackageJsonSafe, registerPluginArtifacts, registerServiceConfig, registerStatusProvider, reloadMiokuConfig, removePluginMetadata, requireService, resetPluginMetadata, resetPluginRuntimeState, resolveCommand, resolveEntry, resolveLocalPluginEntry, resolveLocalPluginPath, rootLogger, runCommand, runCommandInherit, segment, sendMessage, manager_default as serviceManager, servicesRegistry, setActiveCommandManager, setBotCwd, setBuiltinPlugins, setPluginMetadata, setPluginRuntimeState, setService, setWritableConfig, start, startRuntime, string2argv, stringifyError, stripAnsi, text, toArray, toId, toUserId, unique, updateMiokuConfig, updateServiceConfig, uuid, version$1 as version, wait, writePackageJson };
5479
6282
  //# sourceMappingURL=index.js.map