@zhin.js/console 1.0.50 → 1.0.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +22 -0
  3. package/browser.tsconfig.json +19 -0
  4. package/client/src/components/PageHeader.tsx +26 -0
  5. package/client/src/components/ui/accordion.tsx +2 -1
  6. package/client/src/components/ui/badge.tsx +1 -3
  7. package/client/src/components/ui/scroll-area.tsx +5 -2
  8. package/client/src/components/ui/select.tsx +7 -3
  9. package/client/src/components/ui/separator.tsx +5 -2
  10. package/client/src/components/ui/tabs.tsx +4 -2
  11. package/client/src/layouts/dashboard.tsx +223 -121
  12. package/client/src/main.tsx +34 -34
  13. package/client/src/pages/bot-detail/MessageBody.tsx +110 -0
  14. package/client/src/pages/bot-detail/date-utils.ts +8 -0
  15. package/client/src/pages/bot-detail/index.tsx +798 -0
  16. package/client/src/pages/bot-detail/types.ts +92 -0
  17. package/client/src/pages/bot-detail/useBotConsole.tsx +600 -0
  18. package/client/src/pages/bots.tsx +111 -73
  19. package/client/src/pages/database/constants.ts +16 -0
  20. package/client/src/pages/database/database-page.tsx +170 -0
  21. package/client/src/pages/database/document-collection-view.tsx +155 -0
  22. package/client/src/pages/database/index.tsx +1 -0
  23. package/client/src/pages/database/json-field.tsx +11 -0
  24. package/client/src/pages/database/kv-bucket-view.tsx +169 -0
  25. package/client/src/pages/database/related-table-view.tsx +221 -0
  26. package/client/src/pages/env.tsx +38 -28
  27. package/client/src/pages/files/code-editor.tsx +85 -0
  28. package/client/src/pages/files/editor-constants.ts +9 -0
  29. package/client/src/pages/files/file-editor.tsx +133 -0
  30. package/client/src/pages/files/file-icons.tsx +25 -0
  31. package/client/src/pages/files/files-page.tsx +92 -0
  32. package/client/src/pages/files/hljs-global.d.ts +10 -0
  33. package/client/src/pages/files/index.tsx +1 -0
  34. package/client/src/pages/files/language.ts +18 -0
  35. package/client/src/pages/files/tree-node.tsx +69 -0
  36. package/client/src/pages/files/use-hljs-theme.ts +23 -0
  37. package/client/src/pages/logs.tsx +77 -22
  38. package/client/src/style.css +144 -0
  39. package/client/src/utils/parseComposerContent.ts +57 -0
  40. package/client/tailwind.config.js +1 -0
  41. package/client/tsconfig.json +3 -1
  42. package/dist/assets/index-COKXlFo2.js +124 -0
  43. package/dist/assets/style-kkLO-vsa.css +3 -0
  44. package/dist/client.js +4262 -1
  45. package/dist/index.html +2 -2
  46. package/dist/radix-ui.js +1261 -1262
  47. package/dist/react-dom-client.js +2243 -2240
  48. package/dist/react-dom.js +15 -15
  49. package/dist/style.css +1 -3
  50. package/lib/index.js +1010 -81
  51. package/lib/transform.js +16 -2
  52. package/lib/websocket.js +845 -28
  53. package/node.tsconfig.json +18 -0
  54. package/package.json +15 -16
  55. package/src/bin.ts +24 -0
  56. package/src/bot-db-models.ts +74 -0
  57. package/src/bot-hub.ts +240 -0
  58. package/src/bot-persistence.ts +270 -0
  59. package/src/build.ts +90 -0
  60. package/src/dev.ts +107 -0
  61. package/src/index.ts +337 -0
  62. package/src/transform.ts +199 -0
  63. package/src/websocket.ts +1369 -0
  64. package/client/src/pages/database.tsx +0 -708
  65. package/client/src/pages/files.tsx +0 -470
  66. package/client/src/pages/login-assist.tsx +0 -225
  67. package/dist/index.js +0 -124
package/lib/index.js CHANGED
@@ -1,15 +1,461 @@
1
- import { usePlugin } from '@zhin.js/core';
1
+ import { usePlugin, Adapter } from '@zhin.js/core';
2
2
  import mime from 'mime';
3
- import * as fs from 'fs';
4
- import fs__default from 'fs';
5
- import * as path from 'path';
6
- import path__default from 'path';
3
+ import * as fs2 from 'fs';
4
+ import fs2__default from 'fs';
5
+ import * as path2 from 'path';
6
+ import path2__default from 'path';
7
7
  import * as crypto from 'crypto';
8
8
  import WebSocket from 'ws';
9
9
  import { transform } from 'esbuild';
10
10
 
11
11
  // src/index.ts
12
+
13
+ // src/bot-db-models.ts
14
+ var ConsoleBotRequestDefinition = {
15
+ id: { type: "integer", primary: true, autoIncrement: true },
16
+ adapter: { type: "text", nullable: false },
17
+ bot_id: { type: "text", nullable: false },
18
+ platform_request_id: { type: "text", nullable: false },
19
+ type: { type: "text", nullable: false },
20
+ sender_id: { type: "text", nullable: false },
21
+ sender_name: { type: "text", nullable: false },
22
+ comment: { type: "text", nullable: false },
23
+ channel_id: { type: "text", nullable: false },
24
+ channel_type: { type: "text", nullable: false },
25
+ created_at: { type: "integer", nullable: false },
26
+ consumed: { type: "integer", nullable: false, default: 0 },
27
+ consumed_at: { type: "integer", nullable: true }
28
+ };
29
+ var ConsoleBotNoticeDefinition = {
30
+ id: { type: "integer", primary: true, autoIncrement: true },
31
+ adapter: { type: "text", nullable: false },
32
+ bot_id: { type: "text", nullable: false },
33
+ notice_type: { type: "text", nullable: false },
34
+ channel_type: { type: "text", nullable: false },
35
+ channel_id: { type: "text", nullable: false },
36
+ payload: { type: "text", nullable: false },
37
+ created_at: { type: "integer", nullable: false },
38
+ consumed: { type: "integer", nullable: false, default: 0 },
39
+ consumed_at: { type: "integer", nullable: true }
40
+ };
41
+ var TABLE_REQUESTS = "console_bot_requests";
42
+ var TABLE_NOTICES = "console_bot_notices";
43
+ function registerBotModels(root3) {
44
+ const defineModel = root3.defineModel;
45
+ if (typeof defineModel !== "function") return;
46
+ defineModel(TABLE_REQUESTS, ConsoleBotRequestDefinition);
47
+ defineModel(TABLE_NOTICES, ConsoleBotNoticeDefinition);
48
+ }
49
+
50
+ // src/bot-persistence.ts
51
+ var DATA_DIR = path2__default.join(process.cwd(), "data");
52
+ var REQ_FILE = path2__default.join(DATA_DIR, "console_bot_requests.json");
53
+ var NOTICE_FILE = path2__default.join(DATA_DIR, "console_bot_notices.json");
54
+ var dbRef = null;
55
+ function initBotPersistence(root3) {
56
+ try {
57
+ dbRef = root3.inject("database");
58
+ } catch {
59
+ dbRef = null;
60
+ }
61
+ }
62
+ function getReqModel() {
63
+ return dbRef?.db?.models?.get(TABLE_REQUESTS);
64
+ }
65
+ function getNoticeModel() {
66
+ return dbRef?.db?.models?.get(TABLE_NOTICES);
67
+ }
68
+ function ensureDir() {
69
+ if (!fs2__default.existsSync(DATA_DIR)) fs2__default.mkdirSync(DATA_DIR, { recursive: true });
70
+ }
71
+ function loadFile(file, empty) {
72
+ try {
73
+ if (!fs2__default.existsSync(file)) return { ...empty };
74
+ const raw = fs2__default.readFileSync(file, "utf-8");
75
+ const j = JSON.parse(raw);
76
+ if (!j || !Array.isArray(j.rows)) return { ...empty };
77
+ return {
78
+ nextId: typeof j.nextId === "number" ? j.nextId : 1,
79
+ rows: j.rows
80
+ };
81
+ } catch {
82
+ return { ...empty };
83
+ }
84
+ }
85
+ function saveFile(file, store) {
86
+ ensureDir();
87
+ fs2__default.writeFileSync(file, JSON.stringify(store, null, 0), "utf-8");
88
+ }
89
+ function normalizeReqRow(r) {
90
+ return {
91
+ ...r,
92
+ id: r.id,
93
+ consumed: r.consumed === 1 ? 1 : 0
94
+ };
95
+ }
96
+ function normalizeNoticeRow(r) {
97
+ return {
98
+ ...r,
99
+ id: r.id,
100
+ consumed: r.consumed === 1 ? 1 : 0
101
+ };
102
+ }
103
+ async function insertRequest(row) {
104
+ const model = getReqModel();
105
+ if (model) {
106
+ const created = await model.create({
107
+ ...row,
108
+ consumed: 0
109
+ });
110
+ const id2 = typeof created?.id === "number" ? created.id : created.id;
111
+ return { ...row, id: id2, consumed: 0 };
112
+ }
113
+ const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
114
+ const id = store.nextId++;
115
+ const full = { ...row, id, consumed: 0 };
116
+ store.rows.push(full);
117
+ saveFile(REQ_FILE, store);
118
+ return full;
119
+ }
120
+ async function insertNotice(row) {
121
+ const model = getNoticeModel();
122
+ if (model) {
123
+ const created = await model.create({
124
+ ...row,
125
+ consumed: 0
126
+ });
127
+ const id2 = typeof created?.id === "number" ? created.id : created.id;
128
+ return { ...row, id: id2, consumed: 0 };
129
+ }
130
+ const store = loadFile(NOTICE_FILE, { nextId: 1, rows: [] });
131
+ const id = store.nextId++;
132
+ const full = { ...row, id, consumed: 0 };
133
+ store.rows.push(full);
134
+ saveFile(NOTICE_FILE, store);
135
+ return full;
136
+ }
137
+ async function listUnconsumedRequests() {
138
+ const model = getReqModel();
139
+ if (model) {
140
+ const rows = await model.select().where({ consumed: 0 });
141
+ return (rows || []).map(normalizeReqRow).sort((a, b) => a.created_at - b.created_at);
142
+ }
143
+ const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
144
+ return store.rows.filter((r) => r.consumed === 0).sort((a, b) => a.created_at - b.created_at);
145
+ }
146
+ async function listUnconsumedNotices() {
147
+ const model = getNoticeModel();
148
+ if (model) {
149
+ const rows = await model.select().where({ consumed: 0 });
150
+ return (rows || []).map(normalizeNoticeRow).sort((a, b) => a.created_at - b.created_at);
151
+ }
152
+ const store = loadFile(NOTICE_FILE, { nextId: 1, rows: [] });
153
+ return store.rows.filter((r) => r.consumed === 0).sort((a, b) => a.created_at - b.created_at);
154
+ }
155
+ async function listRequestsForBot(adapter, botId) {
156
+ const all = await listUnconsumedRequests();
157
+ return all.filter((r) => r.adapter === adapter && r.bot_id === botId);
158
+ }
159
+ async function markRequestsConsumed(ids) {
160
+ if (!ids.length) return;
161
+ const model = getReqModel();
162
+ const now = Date.now();
163
+ if (model) {
164
+ for (const id of ids) {
165
+ await model.update({ consumed: 1, consumed_at: now }).where({ id });
166
+ }
167
+ return;
168
+ }
169
+ const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
170
+ const set = new Set(ids);
171
+ for (const r of store.rows) {
172
+ if (set.has(r.id) && r.consumed === 0) {
173
+ r.consumed = 1;
174
+ r.consumed_at = now;
175
+ }
176
+ }
177
+ saveFile(REQ_FILE, store);
178
+ }
179
+ async function markNoticesConsumed(ids) {
180
+ if (!ids.length) return;
181
+ const model = getNoticeModel();
182
+ const now = Date.now();
183
+ if (model) {
184
+ for (const id of ids) {
185
+ await model.update({ consumed: 1, consumed_at: now }).where({ id });
186
+ }
187
+ return;
188
+ }
189
+ const store = loadFile(NOTICE_FILE, { nextId: 1, rows: [] });
190
+ const set = new Set(ids);
191
+ for (const r of store.rows) {
192
+ if (set.has(r.id) && r.consumed === 0) {
193
+ r.consumed = 1;
194
+ r.consumed_at = now;
195
+ }
196
+ }
197
+ saveFile(NOTICE_FILE, store);
198
+ }
199
+ async function findRequestRow(adapter, botId, platformRequestId) {
200
+ const model = getReqModel();
201
+ if (model) {
202
+ const rows = await model.select().where({
203
+ adapter,
204
+ bot_id: botId,
205
+ platform_request_id: platformRequestId,
206
+ consumed: 0
207
+ });
208
+ return rows?.[0] ? normalizeReqRow(rows[0]) : void 0;
209
+ }
210
+ const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
211
+ const r = store.rows.find(
212
+ (x) => x.adapter === adapter && x.bot_id === botId && x.platform_request_id === platformRequestId && x.consumed === 0
213
+ );
214
+ return r;
215
+ }
216
+ async function getRequestRowById(id) {
217
+ const model = getReqModel();
218
+ if (model) {
219
+ const rows = await model.select().where({ id });
220
+ return rows?.[0] ? normalizeReqRow(rows[0]) : void 0;
221
+ }
222
+ const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
223
+ const r = store.rows.find((x) => x.id === id);
224
+ return r;
225
+ }
226
+
227
+ // src/bot-hub.ts
228
+ var wssRef = null;
229
+ var hubInited = false;
230
+ function setBotHubWss(wss) {
231
+ wssRef = wss;
232
+ }
233
+ function broadcast(obj) {
234
+ const msg = JSON.stringify(obj);
235
+ const clients = wssRef?.clients;
236
+ if (!clients) return;
237
+ const list = clients instanceof Set ? [...clients] : clients;
238
+ for (const ws of list) {
239
+ if (ws.readyState === 1) ws.send(msg);
240
+ }
241
+ }
242
+ function requestMemoryKey(adapter, botId, platformId) {
243
+ return `${adapter}:${botId}:${platformId}`;
244
+ }
245
+ var pendingRequestObjects = /* @__PURE__ */ new Map();
246
+ async function storePendingRequest(adapter, botId, req) {
247
+ const platformId = req.$id;
248
+ const key = requestMemoryKey(adapter, botId, platformId);
249
+ pendingRequestObjects.set(key, req);
250
+ const row = await insertRequest({
251
+ adapter,
252
+ bot_id: botId,
253
+ platform_request_id: platformId,
254
+ type: String(req.$type),
255
+ sender_id: String(req.$sender?.id ?? ""),
256
+ sender_name: String(req.$sender?.name ?? ""),
257
+ comment: String(req.$comment ?? ""),
258
+ channel_id: String(req.$channel?.id ?? ""),
259
+ channel_type: String(req.$channel?.type ?? "private"),
260
+ created_at: typeof req.$timestamp === "number" ? req.$timestamp : Date.now()
261
+ });
262
+ return row;
263
+ }
264
+ function rowToRequestPushData(row, canAct) {
265
+ return {
266
+ id: row.id,
267
+ adapter: row.adapter,
268
+ botId: row.bot_id,
269
+ platformRequestId: row.platform_request_id,
270
+ type: row.type,
271
+ sender: { id: row.sender_id, name: row.sender_name },
272
+ comment: row.comment,
273
+ channel: { id: row.channel_id, type: row.channel_type },
274
+ timestamp: row.created_at,
275
+ canAct
276
+ };
277
+ }
278
+ async function onRequestReceived(adapter, botId, req) {
279
+ const row = await storePendingRequest(adapter, botId, req);
280
+ const key = requestMemoryKey(adapter, botId, req.$id);
281
+ const canAct = pendingRequestObjects.has(key);
282
+ broadcast({ type: "bot:request", data: rowToRequestPushData(row, canAct) });
283
+ }
284
+ async function onNoticeReceived(adapter, botId, notice) {
285
+ let raw = {};
286
+ try {
287
+ raw = Object.fromEntries(
288
+ Object.entries(notice).filter(
289
+ ([k]) => !k.startsWith("$") && k !== "adapter" && k !== "bot"
290
+ )
291
+ );
292
+ } catch {
293
+ raw = {};
294
+ }
295
+ let payload;
296
+ try {
297
+ payload = JSON.stringify({
298
+ type: notice.$type,
299
+ subType: notice.$subType,
300
+ channel: notice.$channel,
301
+ raw
302
+ });
303
+ } catch {
304
+ payload = JSON.stringify({ type: notice.$type, error: "serialize_failed" });
305
+ }
306
+ const row = await insertNotice({
307
+ adapter,
308
+ bot_id: botId,
309
+ notice_type: String(notice.$type ?? "unknown"),
310
+ channel_type: String(notice.$channel?.type ?? ""),
311
+ channel_id: String(notice.$channel?.id ?? ""),
312
+ payload,
313
+ created_at: typeof notice.$timestamp === "number" ? notice.$timestamp : Date.now()
314
+ });
315
+ broadcast({
316
+ type: "bot:notice",
317
+ data: {
318
+ id: row.id,
319
+ adapter: row.adapter,
320
+ botId: row.bot_id,
321
+ noticeType: row.notice_type,
322
+ channel: { id: row.channel_id, type: row.channel_type },
323
+ payload: row.payload,
324
+ timestamp: row.created_at
325
+ }
326
+ });
327
+ }
328
+ function getPendingRequest(adapter, botId, platformRequestId) {
329
+ return pendingRequestObjects.get(requestMemoryKey(adapter, botId, platformRequestId));
330
+ }
331
+ function removePendingRequest(adapter, botId, platformRequestId) {
332
+ pendingRequestObjects.delete(requestMemoryKey(adapter, botId, platformRequestId));
333
+ }
334
+ async function markRequestConsumedByPlatformId(adapter, botId, platformRequestId) {
335
+ const row = await findRequestRow(adapter, botId, platformRequestId);
336
+ if (row) await markRequestsConsumed([row.id]);
337
+ removePendingRequest(adapter, botId, platformRequestId);
338
+ }
339
+ async function sendCatchUpToClient(ws) {
340
+ const reqs = await listUnconsumedRequests();
341
+ for (const row of reqs) {
342
+ const canAct = pendingRequestObjects.has(
343
+ requestMemoryKey(row.adapter, row.bot_id, row.platform_request_id)
344
+ );
345
+ if (ws.readyState === 1) {
346
+ ws.send(
347
+ JSON.stringify({
348
+ type: "bot:request",
349
+ data: rowToRequestPushData(row, canAct)
350
+ })
351
+ );
352
+ }
353
+ }
354
+ const notices = await listUnconsumedNotices();
355
+ for (const row of notices) {
356
+ if (ws.readyState === 1) {
357
+ ws.send(
358
+ JSON.stringify({
359
+ type: "bot:notice",
360
+ data: {
361
+ id: row.id,
362
+ adapter: row.adapter,
363
+ botId: row.bot_id,
364
+ noticeType: row.notice_type,
365
+ channel: { id: row.channel_id, type: row.channel_type },
366
+ payload: row.payload,
367
+ timestamp: row.created_at
368
+ }
369
+ })
370
+ );
371
+ }
372
+ }
373
+ }
374
+ function initBotHub(root3) {
375
+ if (hubInited) return;
376
+ hubInited = true;
377
+ const handlerReq = (req) => {
378
+ const adapter = String(req.$adapter);
379
+ const botId = String(req.$bot);
380
+ onRequestReceived(adapter, botId, req);
381
+ };
382
+ const handlerNotice = (notice) => {
383
+ const adapter = String(notice.$adapter);
384
+ const botId = String(notice.$bot);
385
+ onNoticeReceived(adapter, botId, notice);
386
+ };
387
+ root3.on("request.receive", handlerReq);
388
+ root3.on("notice.receive", handlerNotice);
389
+ const adapterNames = root3.adapters ? [...root3.adapters] : [];
390
+ const inject2 = root3.inject;
391
+ if (inject2 && typeof inject2 === "function" && adapterNames.length > 0) {
392
+ for (const name of adapterNames) {
393
+ try {
394
+ const ad = inject2(name);
395
+ if (ad && typeof ad.on === "function") {
396
+ ad.on("message.receive", (msg) => {
397
+ const payload = {
398
+ type: "bot:message",
399
+ data: {
400
+ adapter: name,
401
+ botId: msg?.$bot,
402
+ channelId: msg?.$channel?.id,
403
+ channelType: msg?.$channel?.type,
404
+ sender: msg?.$sender,
405
+ content: msg?.$content ?? [],
406
+ timestamp: typeof msg?.$timestamp === "number" ? msg.$timestamp : Date.now()
407
+ }
408
+ };
409
+ broadcast(payload);
410
+ });
411
+ }
412
+ } catch {
413
+ }
414
+ }
415
+ }
416
+ }
417
+
418
+ // src/websocket.ts
12
419
  var { root, logger } = usePlugin();
420
+ function collectBotsList() {
421
+ const bots = [];
422
+ for (const name of root.adapters) {
423
+ const adapter = root.inject(name);
424
+ if (adapter instanceof Adapter) {
425
+ for (const [botName, bot] of adapter.bots.entries()) {
426
+ bots.push({
427
+ name: botName,
428
+ adapter: String(name),
429
+ connected: !!bot.$connected,
430
+ status: bot.$connected ? "online" : "offline"
431
+ });
432
+ }
433
+ }
434
+ }
435
+ return bots;
436
+ }
437
+ async function collectBotsListWithPending() {
438
+ const bots = collectBotsList();
439
+ let reqs = [];
440
+ let notices = [];
441
+ try {
442
+ [reqs, notices] = await Promise.all([listUnconsumedRequests(), listUnconsumedNotices()]);
443
+ } catch {
444
+ }
445
+ return bots.map((bot) => {
446
+ const pendingRequestCount = reqs.filter(
447
+ (r) => r.adapter === bot.adapter && r.bot_id === bot.name
448
+ ).length;
449
+ const pendingNoticeCount = notices.filter(
450
+ (n) => n.adapter === bot.adapter && n.bot_id === bot.name
451
+ ).length;
452
+ return {
453
+ ...bot,
454
+ pendingRequestCount,
455
+ pendingNoticeCount
456
+ };
457
+ });
458
+ }
13
459
  var ENV_WHITELIST = [".env", ".env.development", ".env.production"];
14
460
  var FILE_MANAGER_ALLOWED = [
15
461
  "src",
@@ -33,7 +479,7 @@ var FILE_MANAGER_BLOCKED = /* @__PURE__ */ new Set([
33
479
  "coverage"
34
480
  ]);
35
481
  function isPathAllowed(relativePath) {
36
- if (relativePath.includes("..") || path__default.isAbsolute(relativePath)) return false;
482
+ if (relativePath.includes("..") || path2__default.isAbsolute(relativePath)) return false;
37
483
  const normalized = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
38
484
  const firstSegment = normalized.split("/")[0];
39
485
  if (FILE_MANAGER_BLOCKED.has(firstSegment)) return false;
@@ -53,15 +499,20 @@ function getPluginKeys() {
53
499
  return Array.from(keys);
54
500
  }
55
501
  function getConfigFilePath() {
56
- return path__default.resolve(process.cwd(), "zhin.config.yml");
502
+ return path2__default.resolve(process.cwd(), "zhin.config.yml");
57
503
  }
58
504
  function setupWebSocket(webServer) {
505
+ setBotHubWss(webServer.ws);
506
+ initBotHub(root);
59
507
  webServer.ws.on("connection", (ws) => {
60
508
  ws.send(JSON.stringify({
61
509
  type: "sync",
62
510
  data: { key: "entries", value: Object.values(webServer.entries) }
63
511
  }));
64
512
  ws.send(JSON.stringify({ type: "init-data", timestamp: Date.now() }));
513
+ void sendCatchUpToClient(ws).catch(
514
+ (e) => logger.warn("[console] bot catch-up failed", e.message)
515
+ );
65
516
  ws.on("message", async (data) => {
66
517
  try {
67
518
  const message = JSON.parse(data.toString());
@@ -93,7 +544,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
93
544
  case "config:get-yaml":
94
545
  try {
95
546
  const filePath = getConfigFilePath();
96
- const yaml = fs__default.existsSync(filePath) ? fs__default.readFileSync(filePath, "utf-8") : "";
547
+ const yaml = fs2__default.existsSync(filePath) ? fs2__default.readFileSync(filePath, "utf-8") : "";
97
548
  ws.send(JSON.stringify({ requestId, data: { yaml, pluginKeys: getPluginKeys() } }));
98
549
  } catch (error) {
99
550
  ws.send(JSON.stringify({ requestId, error: `Failed to read config: ${error.message}` }));
@@ -107,7 +558,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
107
558
  break;
108
559
  }
109
560
  const filePath = getConfigFilePath();
110
- fs__default.writeFileSync(filePath, yaml, "utf-8");
561
+ fs2__default.writeFileSync(filePath, yaml, "utf-8");
111
562
  const configService2 = root.inject("config");
112
563
  const loader = configService2.configs.get("zhin.config.yml");
113
564
  if (loader) loader.load();
@@ -230,7 +681,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
230
681
  const cwd = process.cwd();
231
682
  const files = ENV_WHITELIST.map((name) => ({
232
683
  name,
233
- exists: fs__default.existsSync(path__default.resolve(cwd, name))
684
+ exists: fs2__default.existsSync(path2__default.resolve(cwd, name))
234
685
  }));
235
686
  ws.send(JSON.stringify({ requestId, data: { files } }));
236
687
  } catch (error) {
@@ -244,8 +695,8 @@ async function handleWebSocketMessage(ws, message, webServer) {
244
695
  ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
245
696
  break;
246
697
  }
247
- const envPath = path__default.resolve(process.cwd(), filename);
248
- const content = fs__default.existsSync(envPath) ? fs__default.readFileSync(envPath, "utf-8") : "";
698
+ const envPath = path2__default.resolve(process.cwd(), filename);
699
+ const content = fs2__default.existsSync(envPath) ? fs2__default.readFileSync(envPath, "utf-8") : "";
249
700
  ws.send(JSON.stringify({ requestId, data: { content } }));
250
701
  } catch (error) {
251
702
  ws.send(JSON.stringify({ requestId, error: `Failed to read env file: ${error.message}` }));
@@ -262,8 +713,8 @@ async function handleWebSocketMessage(ws, message, webServer) {
262
713
  ws.send(JSON.stringify({ requestId, error: "content field is required" }));
263
714
  break;
264
715
  }
265
- const envPath = path__default.resolve(process.cwd(), filename);
266
- fs__default.writeFileSync(envPath, content, "utf-8");
716
+ const envPath = path2__default.resolve(process.cwd(), filename);
717
+ fs2__default.writeFileSync(envPath, content, "utf-8");
267
718
  ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u73AF\u5883\u53D8\u91CF\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
268
719
  } catch (error) {
269
720
  ws.send(JSON.stringify({ requestId, error: `Failed to save env file: ${error.message}` }));
@@ -288,12 +739,12 @@ async function handleWebSocketMessage(ws, message, webServer) {
288
739
  ws.send(JSON.stringify({ requestId, error: `Access denied: ${fp}` }));
289
740
  break;
290
741
  }
291
- const absPath = path__default.resolve(process.cwd(), fp);
292
- if (!fs__default.existsSync(absPath)) {
742
+ const absPath = path2__default.resolve(process.cwd(), fp);
743
+ if (!fs2__default.existsSync(absPath)) {
293
744
  ws.send(JSON.stringify({ requestId, error: `File not found: ${fp}` }));
294
745
  break;
295
746
  }
296
- const stat = fs__default.statSync(absPath);
747
+ const stat = fs2__default.statSync(absPath);
297
748
  if (!stat.isFile()) {
298
749
  ws.send(JSON.stringify({ requestId, error: `Not a file: ${fp}` }));
299
750
  break;
@@ -302,7 +753,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
302
753
  ws.send(JSON.stringify({ requestId, error: `File too large: ${(stat.size / 1024).toFixed(0)}KB (max 1MB)` }));
303
754
  break;
304
755
  }
305
- const fileContent = fs__default.readFileSync(absPath, "utf-8");
756
+ const fileContent = fs2__default.readFileSync(absPath, "utf-8");
306
757
  ws.send(JSON.stringify({ requestId, data: { content: fileContent, size: stat.size } }));
307
758
  } catch (error) {
308
759
  ws.send(JSON.stringify({ requestId, error: `Failed to read file: ${error.message}` }));
@@ -319,12 +770,12 @@ async function handleWebSocketMessage(ws, message, webServer) {
319
770
  ws.send(JSON.stringify({ requestId, error: "content field is required" }));
320
771
  break;
321
772
  }
322
- const absPath = path__default.resolve(process.cwd(), fp);
323
- const dir = path__default.dirname(absPath);
324
- if (!fs__default.existsSync(dir)) {
325
- fs__default.mkdirSync(dir, { recursive: true });
773
+ const absPath = path2__default.resolve(process.cwd(), fp);
774
+ const dir = path2__default.dirname(absPath);
775
+ if (!fs2__default.existsSync(dir)) {
776
+ fs2__default.mkdirSync(dir, { recursive: true });
326
777
  }
327
- fs__default.writeFileSync(absPath, fileContent, "utf-8");
778
+ fs2__default.writeFileSync(absPath, fileContent, "utf-8");
328
779
  ws.send(JSON.stringify({ requestId, data: { success: true, message: `\u6587\u4EF6\u5DF2\u4FDD\u5B58: ${fp}` } }));
329
780
  } catch (error) {
330
781
  ws.send(JSON.stringify({ requestId, error: `Failed to save file: ${error.message}` }));
@@ -467,6 +918,494 @@ async function handleWebSocketMessage(ws, message, webServer) {
467
918
  ws.send(JSON.stringify({ requestId, error: `Failed to get entries: ${error.message}` }));
468
919
  }
469
920
  break;
921
+ // ================================================================
922
+ // 机器人管理(WebSocket)
923
+ // ================================================================
924
+ case "bot:list": {
925
+ try {
926
+ const botsWithPending = await collectBotsListWithPending();
927
+ ws.send(JSON.stringify({ requestId, data: { bots: botsWithPending } }));
928
+ } catch (error) {
929
+ ws.send(JSON.stringify({ requestId, error: error.message }));
930
+ }
931
+ break;
932
+ }
933
+ case "bot:info": {
934
+ try {
935
+ const d = message.data || {};
936
+ const { adapter, botId } = d;
937
+ if (!adapter || !botId) {
938
+ ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
939
+ break;
940
+ }
941
+ const ad = root.inject(adapter);
942
+ if (!(ad instanceof Adapter)) {
943
+ ws.send(JSON.stringify({ requestId, error: "adapter not found" }));
944
+ break;
945
+ }
946
+ const bot = ad.bots.get(botId);
947
+ if (!bot) {
948
+ ws.send(JSON.stringify({ requestId, error: "bot not found" }));
949
+ break;
950
+ }
951
+ ws.send(
952
+ JSON.stringify({
953
+ requestId,
954
+ data: {
955
+ name: botId,
956
+ adapter: String(adapter),
957
+ connected: !!bot.$connected,
958
+ status: bot.$connected ? "online" : "offline"
959
+ }
960
+ })
961
+ );
962
+ } catch (error) {
963
+ ws.send(JSON.stringify({ requestId, error: error.message }));
964
+ }
965
+ break;
966
+ }
967
+ case "bot:sendMessage": {
968
+ try {
969
+ const d = message.data || {};
970
+ const { adapter, botId, id, type: msgType, content } = d;
971
+ if (!adapter || !botId || !id || !msgType || content === void 0) {
972
+ ws.send(
973
+ JSON.stringify({
974
+ requestId,
975
+ error: "adapter, botId, id, type, content required"
976
+ })
977
+ );
978
+ break;
979
+ }
980
+ const ad = root.inject(adapter);
981
+ if (!(ad instanceof Adapter)) {
982
+ ws.send(JSON.stringify({ requestId, error: "adapter not found" }));
983
+ break;
984
+ }
985
+ const normalized = typeof content === "string" ? content : Array.isArray(content) ? content : String(content);
986
+ const messageId = await ad.sendMessage({
987
+ context: adapter,
988
+ bot: botId,
989
+ id: String(id),
990
+ type: msgType,
991
+ content: normalized
992
+ });
993
+ ws.send(JSON.stringify({ requestId, data: { messageId } }));
994
+ } catch (error) {
995
+ ws.send(JSON.stringify({ requestId, error: error.message }));
996
+ }
997
+ break;
998
+ }
999
+ case "bot:friends":
1000
+ case "bot:groups": {
1001
+ try {
1002
+ const d = message.data || {};
1003
+ const { adapter, botId } = d;
1004
+ if (!adapter || !botId) {
1005
+ ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
1006
+ break;
1007
+ }
1008
+ if (adapter !== "icqq") {
1009
+ ws.send(JSON.stringify({ requestId, error: "not supported for this adapter" }));
1010
+ break;
1011
+ }
1012
+ const ad = root.inject("icqq");
1013
+ const bot = ad?.bots?.get?.(botId);
1014
+ if (!bot) {
1015
+ ws.send(JSON.stringify({ requestId, error: "bot not found" }));
1016
+ break;
1017
+ }
1018
+ if (type === "bot:friends") {
1019
+ const fl = bot.fl;
1020
+ const friends = Array.from((fl || /* @__PURE__ */ new Map()).values()).map((f) => ({
1021
+ user_id: f.user_id,
1022
+ nickname: f.nickname,
1023
+ remark: f.remark
1024
+ }));
1025
+ ws.send(JSON.stringify({ requestId, data: { friends, count: friends.length } }));
1026
+ } else {
1027
+ const gl = bot.gl;
1028
+ const groups = Array.from((gl || /* @__PURE__ */ new Map()).values()).map((g) => ({
1029
+ group_id: g.group_id,
1030
+ name: g.name
1031
+ }));
1032
+ ws.send(JSON.stringify({ requestId, data: { groups, count: groups.length } }));
1033
+ }
1034
+ } catch (error) {
1035
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1036
+ }
1037
+ break;
1038
+ }
1039
+ case "bot:channels": {
1040
+ try {
1041
+ const d = message.data || {};
1042
+ const { adapter, botId } = d;
1043
+ if (!adapter || !botId) {
1044
+ ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
1045
+ break;
1046
+ }
1047
+ if (adapter === "icqq") {
1048
+ ws.send(JSON.stringify({ requestId, error: "channels not supported for icqq" }));
1049
+ break;
1050
+ }
1051
+ const ad = root.inject(adapter);
1052
+ const bot = ad?.bots?.get?.(botId);
1053
+ if (!bot) {
1054
+ ws.send(JSON.stringify({ requestId, error: "bot not found" }));
1055
+ break;
1056
+ }
1057
+ const channels = [];
1058
+ if (adapter === "qq" && typeof bot.getGuilds === "function" && typeof bot.getChannels === "function") {
1059
+ const guilds = await bot.getGuilds() || [];
1060
+ for (const g of guilds) {
1061
+ const gid = g?.id ?? g?.guild_id ?? String(g);
1062
+ const chs = await bot.getChannels(gid) || [];
1063
+ for (const c of chs) {
1064
+ channels.push({
1065
+ id: String(c?.id ?? c?.channel_id ?? c),
1066
+ name: String(c?.name ?? c?.channel_name ?? c?.id ?? "")
1067
+ });
1068
+ }
1069
+ }
1070
+ } else if (typeof ad?.listChannels === "function") {
1071
+ const result = await ad.listChannels(botId);
1072
+ if (Array.isArray(result)) channels.push(...result.map((c) => ({ id: String(c?.id ?? c), name: String(c?.name ?? c?.id ?? "") })));
1073
+ else if (result?.channels) channels.push(...result.channels.map((c) => ({ id: String(c?.id ?? c), name: String(c?.name ?? c?.id ?? "") })));
1074
+ }
1075
+ ws.send(JSON.stringify({ requestId, data: { channels, count: channels.length } }));
1076
+ } catch (error) {
1077
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1078
+ }
1079
+ break;
1080
+ }
1081
+ case "bot:deleteFriend": {
1082
+ try {
1083
+ const d = message.data || {};
1084
+ const { adapter, botId, userId } = d;
1085
+ if (!adapter || !botId || !userId) {
1086
+ ws.send(JSON.stringify({ requestId, error: "adapter, botId, userId required" }));
1087
+ break;
1088
+ }
1089
+ const ad = root.inject(adapter);
1090
+ const bot = ad?.bots?.get?.(botId);
1091
+ if (!bot) {
1092
+ ws.send(JSON.stringify({ requestId, error: "bot not found" }));
1093
+ break;
1094
+ }
1095
+ if (adapter === "icqq" && typeof bot.deleteFriend === "function") {
1096
+ await bot.deleteFriend(Number(userId));
1097
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1098
+ } else if (adapter === "icqq" && typeof bot.delete_friend === "function") {
1099
+ await bot.delete_friend(Number(userId));
1100
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1101
+ } else {
1102
+ ws.send(JSON.stringify({ requestId, error: "\u5F53\u524D\u9002\u914D\u5668\u6682\u4E0D\u652F\u6301\u5220\u9664\u597D\u53CB" }));
1103
+ }
1104
+ } catch (error) {
1105
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1106
+ }
1107
+ break;
1108
+ }
1109
+ case "bot:requests": {
1110
+ try {
1111
+ const d = message.data || {};
1112
+ const { adapter, botId } = d;
1113
+ if (!adapter || !botId) {
1114
+ ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
1115
+ break;
1116
+ }
1117
+ const rows = await listRequestsForBot(String(adapter), String(botId));
1118
+ ws.send(
1119
+ JSON.stringify({
1120
+ requestId,
1121
+ data: {
1122
+ requests: rows.map((r) => ({
1123
+ id: r.id,
1124
+ platformRequestId: r.platform_request_id,
1125
+ type: r.type,
1126
+ sender: { id: r.sender_id, name: r.sender_name },
1127
+ comment: r.comment,
1128
+ channel: { id: r.channel_id, type: r.channel_type },
1129
+ timestamp: r.created_at
1130
+ }))
1131
+ }
1132
+ })
1133
+ );
1134
+ } catch (error) {
1135
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1136
+ }
1137
+ break;
1138
+ }
1139
+ case "bot:requestApprove":
1140
+ case "bot:requestReject": {
1141
+ try {
1142
+ const d = message.data || {};
1143
+ const { adapter, botId, requestId: platformReqId, remark, reason } = d;
1144
+ if (!adapter || !botId || !platformReqId) {
1145
+ ws.send(
1146
+ JSON.stringify({
1147
+ requestId,
1148
+ error: "adapter, botId, requestId required"
1149
+ })
1150
+ );
1151
+ break;
1152
+ }
1153
+ const req = getPendingRequest(String(adapter), String(botId), String(platformReqId));
1154
+ if (!req) {
1155
+ ws.send(
1156
+ JSON.stringify({
1157
+ requestId,
1158
+ error: "request not in memory (restart?) \u2014 use bot:requestConsumed to dismiss"
1159
+ })
1160
+ );
1161
+ break;
1162
+ }
1163
+ if (type === "bot:requestApprove") await req.$approve(remark);
1164
+ else await req.$reject(reason);
1165
+ await markRequestConsumedByPlatformId(String(adapter), String(botId), String(platformReqId));
1166
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1167
+ } catch (error) {
1168
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1169
+ }
1170
+ break;
1171
+ }
1172
+ case "bot:requestConsumed": {
1173
+ try {
1174
+ const d = message.data || {};
1175
+ const ids = d.ids ?? (d.id != null ? [d.id] : []);
1176
+ if (!Array.isArray(ids) || !ids.length) {
1177
+ ws.send(JSON.stringify({ requestId, error: "id or ids required" }));
1178
+ break;
1179
+ }
1180
+ const numIds = ids.map(Number);
1181
+ for (const id of numIds) {
1182
+ const row = await getRequestRowById(id);
1183
+ if (row && row.consumed === 0) {
1184
+ removePendingRequest(row.adapter, row.bot_id, row.platform_request_id);
1185
+ }
1186
+ }
1187
+ await markRequestsConsumed(numIds);
1188
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1189
+ } catch (error) {
1190
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1191
+ }
1192
+ break;
1193
+ }
1194
+ case "bot:noticeConsumed": {
1195
+ try {
1196
+ const d = message.data || {};
1197
+ const ids = d.ids ?? (d.id != null ? [d.id] : []);
1198
+ if (!Array.isArray(ids) || !ids.length) {
1199
+ ws.send(JSON.stringify({ requestId, error: "id or ids required" }));
1200
+ break;
1201
+ }
1202
+ await markNoticesConsumed(ids.map(Number));
1203
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1204
+ } catch (error) {
1205
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1206
+ }
1207
+ break;
1208
+ }
1209
+ case "bot:inboxMessages": {
1210
+ try {
1211
+ const d = message.data || {};
1212
+ const { adapter, botId, channelId, channelType, limit = 50, beforeId, beforeTs } = d;
1213
+ if (!adapter || !botId || !channelId || !channelType) {
1214
+ ws.send(JSON.stringify({ requestId, error: "adapter, botId, channelId, channelType required" }));
1215
+ break;
1216
+ }
1217
+ let db;
1218
+ try {
1219
+ db = root.inject("database");
1220
+ } catch {
1221
+ ws.send(JSON.stringify({ requestId, data: { messages: [], inboxEnabled: false } }));
1222
+ break;
1223
+ }
1224
+ const MessageModel = db?.models?.get("unified_inbox_message");
1225
+ if (!MessageModel) {
1226
+ ws.send(JSON.stringify({ requestId, data: { messages: [], inboxEnabled: false } }));
1227
+ break;
1228
+ }
1229
+ const where = {
1230
+ adapter: String(adapter),
1231
+ bot_id: String(botId),
1232
+ channel_id: String(channelId),
1233
+ channel_type: String(channelType)
1234
+ };
1235
+ if (beforeTs != null) where.created_at = { $lt: Number(beforeTs) };
1236
+ if (beforeId != null) where.id = { $lt: Number(beforeId) };
1237
+ let q = MessageModel.select().where(where).orderBy("created_at", "DESC").limit(Math.min(Number(limit) || 50, 100));
1238
+ const rows = await (typeof q.then === "function" ? q : Promise.resolve(q));
1239
+ const messages = (rows || []).map((r) => ({
1240
+ id: r.id,
1241
+ platform_message_id: r.platform_message_id,
1242
+ sender_id: r.sender_id,
1243
+ sender_name: r.sender_name,
1244
+ content: r.content,
1245
+ raw: r.raw,
1246
+ created_at: r.created_at
1247
+ }));
1248
+ ws.send(JSON.stringify({ requestId, data: { messages, inboxEnabled: true } }));
1249
+ } catch (error) {
1250
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1251
+ }
1252
+ break;
1253
+ }
1254
+ case "bot:inboxRequests": {
1255
+ try {
1256
+ const d = message.data || {};
1257
+ const { adapter, botId, limit = 30, offset = 0 } = d;
1258
+ if (!adapter || !botId) {
1259
+ ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
1260
+ break;
1261
+ }
1262
+ let db;
1263
+ try {
1264
+ db = root.inject("database");
1265
+ } catch {
1266
+ ws.send(JSON.stringify({ requestId, data: { requests: [], inboxEnabled: false } }));
1267
+ break;
1268
+ }
1269
+ const RequestModel = db?.models?.get("unified_inbox_request");
1270
+ if (!RequestModel) {
1271
+ ws.send(JSON.stringify({ requestId, data: { requests: [], inboxEnabled: false } }));
1272
+ break;
1273
+ }
1274
+ const where = { adapter: String(adapter), bot_id: String(botId) };
1275
+ const limitNum = Math.min(Number(limit) || 30, 100);
1276
+ const offsetNum = Math.max(0, Number(offset) || 0);
1277
+ let q = RequestModel.select().where(where).orderBy("created_at", "DESC").limit(limitNum).offset(offsetNum);
1278
+ const rows = await (typeof q.then === "function" ? q : Promise.resolve(q));
1279
+ const requests = (rows || []).map((r) => ({
1280
+ id: r.id,
1281
+ platform_request_id: r.platform_request_id,
1282
+ type: r.type,
1283
+ sub_type: r.sub_type,
1284
+ channel_id: r.channel_id,
1285
+ channel_type: r.channel_type,
1286
+ sender_id: r.sender_id,
1287
+ sender_name: r.sender_name,
1288
+ comment: r.comment,
1289
+ created_at: r.created_at,
1290
+ resolved: r.resolved,
1291
+ resolved_at: r.resolved_at
1292
+ }));
1293
+ ws.send(JSON.stringify({ requestId, data: { requests, inboxEnabled: true } }));
1294
+ } catch (error) {
1295
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1296
+ }
1297
+ break;
1298
+ }
1299
+ case "bot:inboxNotices": {
1300
+ try {
1301
+ const d = message.data || {};
1302
+ const { adapter, botId, limit = 30, offset = 0 } = d;
1303
+ if (!adapter || !botId) {
1304
+ ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
1305
+ break;
1306
+ }
1307
+ let db;
1308
+ try {
1309
+ db = root.inject("database");
1310
+ } catch {
1311
+ ws.send(JSON.stringify({ requestId, data: { notices: [], inboxEnabled: false } }));
1312
+ break;
1313
+ }
1314
+ const NoticeModel = db?.models?.get("unified_inbox_notice");
1315
+ if (!NoticeModel) {
1316
+ ws.send(JSON.stringify({ requestId, data: { notices: [], inboxEnabled: false } }));
1317
+ break;
1318
+ }
1319
+ const where = { adapter: String(adapter), bot_id: String(botId) };
1320
+ const limitNum = Math.min(Number(limit) || 30, 100);
1321
+ const offsetNum = Math.max(0, Number(offset) || 0);
1322
+ let q = NoticeModel.select().where(where).orderBy("created_at", "DESC").limit(limitNum).offset(offsetNum);
1323
+ const rows = await (typeof q.then === "function" ? q : Promise.resolve(q));
1324
+ const notices = (rows || []).map((r) => ({
1325
+ id: r.id,
1326
+ platform_notice_id: r.platform_notice_id,
1327
+ type: r.type,
1328
+ sub_type: r.sub_type,
1329
+ channel_id: r.channel_id,
1330
+ channel_type: r.channel_type,
1331
+ operator_id: r.operator_id,
1332
+ operator_name: r.operator_name,
1333
+ target_id: r.target_id,
1334
+ target_name: r.target_name,
1335
+ payload: r.payload,
1336
+ created_at: r.created_at
1337
+ }));
1338
+ ws.send(JSON.stringify({ requestId, data: { notices, inboxEnabled: true } }));
1339
+ } catch (error) {
1340
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1341
+ }
1342
+ break;
1343
+ }
1344
+ case "bot:groupMembers":
1345
+ case "bot:groupKick":
1346
+ case "bot:groupMute":
1347
+ case "bot:groupAdmin": {
1348
+ try {
1349
+ const d = message.data || {};
1350
+ const { adapter, botId, groupId, userId, duration, enable } = d;
1351
+ if (!adapter || !botId || !groupId) {
1352
+ ws.send(
1353
+ JSON.stringify({ requestId, error: "adapter, botId, groupId required" })
1354
+ );
1355
+ break;
1356
+ }
1357
+ const ad = root.inject(adapter);
1358
+ if (!ad) {
1359
+ ws.send(JSON.stringify({ requestId, error: "adapter not found" }));
1360
+ break;
1361
+ }
1362
+ const gid = String(groupId);
1363
+ if (type === "bot:groupMembers") {
1364
+ if (typeof ad.listMembers !== "function") {
1365
+ ws.send(JSON.stringify({ requestId, error: "adapter does not support listMembers" }));
1366
+ break;
1367
+ }
1368
+ const r = await ad.listMembers(botId, gid);
1369
+ ws.send(JSON.stringify({ requestId, data: r }));
1370
+ } else if (type === "bot:groupKick") {
1371
+ if (!userId) {
1372
+ ws.send(JSON.stringify({ requestId, error: "userId required" }));
1373
+ break;
1374
+ }
1375
+ if (typeof ad.kickMember !== "function") {
1376
+ ws.send(JSON.stringify({ requestId, error: "adapter does not support kickMember" }));
1377
+ break;
1378
+ }
1379
+ await ad.kickMember(botId, gid, String(userId));
1380
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1381
+ } else if (type === "bot:groupMute") {
1382
+ if (!userId) {
1383
+ ws.send(JSON.stringify({ requestId, error: "userId required" }));
1384
+ break;
1385
+ }
1386
+ if (typeof ad.muteMember !== "function") {
1387
+ ws.send(JSON.stringify({ requestId, error: "adapter does not support muteMember" }));
1388
+ break;
1389
+ }
1390
+ await ad.muteMember(botId, gid, String(userId), duration ?? 600);
1391
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1392
+ } else {
1393
+ if (!userId) {
1394
+ ws.send(JSON.stringify({ requestId, error: "userId required" }));
1395
+ break;
1396
+ }
1397
+ if (typeof ad.setAdmin !== "function") {
1398
+ ws.send(JSON.stringify({ requestId, error: "adapter does not support setAdmin" }));
1399
+ break;
1400
+ }
1401
+ await ad.setAdmin(botId, gid, String(userId), enable !== false);
1402
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
1403
+ }
1404
+ } catch (error) {
1405
+ ws.send(JSON.stringify({ requestId, error: error.message }));
1406
+ }
1407
+ break;
1408
+ }
470
1409
  default:
471
1410
  ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
472
1411
  }
@@ -628,13 +1567,13 @@ async function kvGetEntries(table) {
628
1567
  }
629
1568
  function buildFileTree(cwd, relativePath, allowed) {
630
1569
  const tree = [];
631
- path__default.resolve(cwd, relativePath);
1570
+ path2__default.resolve(cwd, relativePath);
632
1571
  for (const entry of allowed) {
633
1572
  const entryRelative = entry;
634
1573
  if (entryRelative.includes("/")) continue;
635
- const absPath = path__default.resolve(cwd, entry);
636
- if (!fs__default.existsSync(absPath)) continue;
637
- const stat = fs__default.statSync(absPath);
1574
+ const absPath = path2__default.resolve(cwd, entry);
1575
+ if (!fs2__default.existsSync(absPath)) continue;
1576
+ const stat = fs2__default.statSync(absPath);
638
1577
  if (stat.isDirectory()) {
639
1578
  tree.push({
640
1579
  name: entryRelative,
@@ -653,9 +1592,9 @@ function buildFileTree(cwd, relativePath, allowed) {
653
1592
  }
654
1593
  function buildDirectoryTree(cwd, relativePath, maxDepth) {
655
1594
  if (maxDepth <= 0) return [];
656
- const absDir = path__default.resolve(cwd, relativePath);
657
- if (!fs__default.existsSync(absDir) || !fs__default.statSync(absDir).isDirectory()) return [];
658
- const entries = fs__default.readdirSync(absDir, { withFileTypes: true });
1595
+ const absDir = path2__default.resolve(cwd, relativePath);
1596
+ if (!fs2__default.existsSync(absDir) || !fs2__default.statSync(absDir).isDirectory()) return [];
1597
+ const entries = fs2__default.readdirSync(absDir, { withFileTypes: true });
659
1598
  const result = [];
660
1599
  for (const entry of entries) {
661
1600
  if (FILE_MANAGER_BLOCKED.has(entry.name) || entry.name.startsWith(".")) continue;
@@ -700,7 +1639,7 @@ var cache = /* @__PURE__ */ new Map();
700
1639
  var TRANSFORMABLE_EXTS = [".ts", ".tsx", ".jsx"];
701
1640
  var RESOLVE_EXTS = [".tsx", ".ts", ".jsx", ".js"];
702
1641
  function isTransformable(filePath) {
703
- const ext = path.extname(filePath).toLowerCase();
1642
+ const ext = path2.extname(filePath).toLowerCase();
704
1643
  return TRANSFORMABLE_EXTS.includes(ext);
705
1644
  }
706
1645
  var IMPORT_RE = /(?:import|export)\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
@@ -709,22 +1648,37 @@ function isRelative(specifier) {
709
1648
  return specifier.startsWith("./") || specifier.startsWith("../");
710
1649
  }
711
1650
  function hasExtension(specifier) {
712
- const base = path.basename(specifier);
1651
+ const base = path2.basename(specifier);
713
1652
  return base.includes(".") && !base.startsWith(".");
714
1653
  }
1654
+ function resolveJsSpecifierToSource(specifier, fromFile) {
1655
+ if (!isRelative(specifier) || !specifier.endsWith(".js")) return specifier;
1656
+ const dir = path2.dirname(fromFile);
1657
+ const jsPath = path2.resolve(dir, specifier);
1658
+ if (fs2.existsSync(jsPath)) return specifier;
1659
+ const base = specifier.slice(0, -3);
1660
+ for (const ext of [".tsx", ".ts", ".jsx"]) {
1661
+ if (fs2.existsSync(path2.resolve(dir, base + ext))) {
1662
+ return base + ext;
1663
+ }
1664
+ }
1665
+ return specifier;
1666
+ }
715
1667
  function resolveRelativeImport(specifier, fromFile) {
716
1668
  if (!isRelative(specifier)) return specifier;
717
- if (hasExtension(specifier)) return specifier;
718
- const dir = path.dirname(fromFile);
719
- const target = path.resolve(dir, specifier);
1669
+ if (hasExtension(specifier)) {
1670
+ return resolveJsSpecifierToSource(specifier, fromFile);
1671
+ }
1672
+ const dir = path2.dirname(fromFile);
1673
+ const target = path2.resolve(dir, specifier);
720
1674
  for (const ext of RESOLVE_EXTS) {
721
- if (fs.existsSync(target + ext)) {
1675
+ if (fs2.existsSync(target + ext)) {
722
1676
  return specifier + ext;
723
1677
  }
724
1678
  }
725
- if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
1679
+ if (fs2.existsSync(target) && fs2.statSync(target).isDirectory()) {
726
1680
  for (const ext of RESOLVE_EXTS) {
727
- if (fs.existsSync(path.join(target, `index${ext}`))) {
1681
+ if (fs2.existsSync(path2.join(target, `index${ext}`))) {
728
1682
  return specifier + `/index${ext}`;
729
1683
  }
730
1684
  }
@@ -736,7 +1690,6 @@ function rewriteImports(code, fromFile) {
736
1690
  code = code.replace(IMPORT_RE, (match, fromSpecifier, dynamicSpecifier) => {
737
1691
  const specifier = fromSpecifier || dynamicSpecifier;
738
1692
  if (!specifier || !isRelative(specifier)) return match;
739
- if (hasExtension(specifier)) return match;
740
1693
  const resolved = resolveRelativeImport(specifier, fromFile);
741
1694
  if (resolved === specifier) return match;
742
1695
  return match.replace(specifier, resolved);
@@ -744,13 +1697,13 @@ function rewriteImports(code, fromFile) {
744
1697
  return code;
745
1698
  }
746
1699
  async function transformFile(filePath) {
747
- const stat = fs.statSync(filePath);
1700
+ const stat = fs2.statSync(filePath);
748
1701
  const cached = cache.get(filePath);
749
1702
  if (cached && cached.mtime === stat.mtimeMs) {
750
1703
  return cached.code;
751
1704
  }
752
- const source = fs.readFileSync(filePath, "utf-8");
753
- const ext = path.extname(filePath).toLowerCase();
1705
+ const source = fs2.readFileSync(filePath, "utf-8");
1706
+ const ext = path2.extname(filePath).toLowerCase();
754
1707
  const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : ext === ".jsx" ? "jsx" : "js";
755
1708
  const result = await transform(source, {
756
1709
  loader,
@@ -775,6 +1728,8 @@ var {
775
1728
  // 默认不延迟加载,避免 addEntry 等功能不可用
776
1729
  } = consoleConfig;
777
1730
  if (enabled) {
1731
+ registerBotModels(root2);
1732
+ initBotPersistence(root2);
778
1733
  const createAddMsg = (key, value) => ({
779
1734
  type: "add",
780
1735
  data: { key, value }
@@ -788,11 +1743,11 @@ if (enabled) {
788
1743
  const genToken = () => crypto.randomBytes(6).toString("hex");
789
1744
  const watchedDirs = /* @__PURE__ */ new Set();
790
1745
  const watchers = [];
791
- path.join(import.meta.dirname, "../client");
792
- const distDir = path.join(import.meta.dirname, "../dist");
1746
+ path2.join(import.meta.dirname, "../client");
1747
+ const distDir = path2.join(import.meta.dirname, "../dist");
793
1748
  const resolveFile = (name) => {
794
- const distPath = path.resolve(distDir, name);
795
- if (fs.existsSync(distPath)) return distPath;
1749
+ const distPath = path2.resolve(distDir, name);
1750
+ if (fs2.existsSync(distPath)) return distPath;
796
1751
  return null;
797
1752
  };
798
1753
  const webServer = {
@@ -800,8 +1755,8 @@ if (enabled) {
800
1755
  addEntry(entry) {
801
1756
  const hash = crypto.randomBytes(8).toString("hex");
802
1757
  const entryFile = typeof entry === "string" ? entry : entry.production;
803
- const dir = path.dirname(entryFile);
804
- const filename = path.basename(entryFile);
1758
+ const dir = path2.dirname(entryFile);
1759
+ const filename = path2.basename(entryFile);
805
1760
  let token;
806
1761
  for (const [t, d] of entryBases) {
807
1762
  if (d === dir) {
@@ -831,32 +1786,6 @@ if (enabled) {
831
1786
  ws: router.ws("/server")
832
1787
  };
833
1788
  logger2.info(`Web \u63A7\u5236\u53F0\u5DF2\u542F\u52A8 (${"\u751F\u4EA7\u6A21\u5F0F, \u9759\u6001\u6587\u4EF6 + \u6309\u9700\u8F6C\u8BD1"})`);
834
- const loginAssist = root2.inject("loginAssist");
835
- if (loginAssist) {
836
- router.get("/api/login-assist/pending", (ctx) => {
837
- ctx.body = loginAssist.listPending();
838
- });
839
- router.post("/api/login-assist/submit", async (ctx) => {
840
- const body = ctx.request?.body;
841
- if (!body?.id) {
842
- ctx.status = 400;
843
- ctx.body = { error: "missing id" };
844
- return;
845
- }
846
- const ok = loginAssist.submit(body.id, body.value ?? "");
847
- ctx.body = { ok };
848
- });
849
- router.post("/api/login-assist/cancel", async (ctx) => {
850
- const body = ctx.request?.body;
851
- if (!body?.id) {
852
- ctx.status = 400;
853
- ctx.body = { error: "missing id" };
854
- return;
855
- }
856
- const ok = loginAssist.cancel(body.id, body.reason);
857
- ctx.body = { ok };
858
- });
859
- }
860
1789
  router.all("*all", async (ctx, next) => {
861
1790
  if (ctx.path.startsWith("/api/") || ctx.path === "/api" || ctx.path.startsWith("/pub/") || ctx.path === "/pub") {
862
1791
  return next();
@@ -869,7 +1798,7 @@ if (enabled) {
869
1798
  const name = ctx.path.slice(1);
870
1799
  const sendFile = async (filename) => {
871
1800
  try {
872
- const stat = fs.statSync(filename);
1801
+ const stat = fs2.statSync(filename);
873
1802
  if (!stat.isFile()) {
874
1803
  ctx.status = 404;
875
1804
  return;
@@ -891,9 +1820,9 @@ if (enabled) {
891
1820
  return;
892
1821
  }
893
1822
  }
894
- ctx.type = path.extname(filename);
1823
+ ctx.type = path2.extname(filename);
895
1824
  ctx.type = mime.getType(filename) || ctx.type;
896
- return ctx.body = fs.createReadStream(filename);
1825
+ return ctx.body = fs2.createReadStream(filename);
897
1826
  };
898
1827
  if (ctx.path.startsWith("/vite/@ext/")) {
899
1828
  const rest = ctx.path.replace("/vite/@ext/", "");
@@ -909,13 +1838,13 @@ if (enabled) {
909
1838
  ctx.status = 404;
910
1839
  return;
911
1840
  }
912
- const fullPath = path.resolve(baseDir, relPath);
913
- const safePfx = baseDir.endsWith(path.sep) ? baseDir : baseDir + path.sep;
1841
+ const fullPath = path2.resolve(baseDir, relPath);
1842
+ const safePfx = baseDir.endsWith(path2.sep) ? baseDir : baseDir + path2.sep;
914
1843
  if (!fullPath.startsWith(safePfx) && fullPath !== baseDir) {
915
1844
  ctx.status = 403;
916
1845
  return;
917
1846
  }
918
- if (fs.existsSync(fullPath)) {
1847
+ if (fs2.existsSync(fullPath)) {
919
1848
  return sendFile(fullPath);
920
1849
  }
921
1850
  ctx.status = 404;
@@ -924,7 +1853,7 @@ if (enabled) {
924
1853
  const resolved = resolveFile(name);
925
1854
  if (resolved) {
926
1855
  try {
927
- const fileState = fs.statSync(resolved);
1856
+ const fileState = fs2.statSync(resolved);
928
1857
  if (fileState.isFile() && !fileState.isSocket() && !fileState.isFIFO()) {
929
1858
  return sendFile(resolved);
930
1859
  }