@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.
- package/CHANGELOG.md +19 -0
- package/README.md +22 -0
- package/browser.tsconfig.json +19 -0
- package/client/src/components/PageHeader.tsx +26 -0
- package/client/src/components/ui/accordion.tsx +2 -1
- package/client/src/components/ui/badge.tsx +1 -3
- package/client/src/components/ui/scroll-area.tsx +5 -2
- package/client/src/components/ui/select.tsx +7 -3
- package/client/src/components/ui/separator.tsx +5 -2
- package/client/src/components/ui/tabs.tsx +4 -2
- package/client/src/layouts/dashboard.tsx +223 -121
- package/client/src/main.tsx +34 -34
- package/client/src/pages/bot-detail/MessageBody.tsx +110 -0
- package/client/src/pages/bot-detail/date-utils.ts +8 -0
- package/client/src/pages/bot-detail/index.tsx +798 -0
- package/client/src/pages/bot-detail/types.ts +92 -0
- package/client/src/pages/bot-detail/useBotConsole.tsx +600 -0
- package/client/src/pages/bots.tsx +111 -73
- package/client/src/pages/database/constants.ts +16 -0
- package/client/src/pages/database/database-page.tsx +170 -0
- package/client/src/pages/database/document-collection-view.tsx +155 -0
- package/client/src/pages/database/index.tsx +1 -0
- package/client/src/pages/database/json-field.tsx +11 -0
- package/client/src/pages/database/kv-bucket-view.tsx +169 -0
- package/client/src/pages/database/related-table-view.tsx +221 -0
- package/client/src/pages/env.tsx +38 -28
- package/client/src/pages/files/code-editor.tsx +85 -0
- package/client/src/pages/files/editor-constants.ts +9 -0
- package/client/src/pages/files/file-editor.tsx +133 -0
- package/client/src/pages/files/file-icons.tsx +25 -0
- package/client/src/pages/files/files-page.tsx +92 -0
- package/client/src/pages/files/hljs-global.d.ts +10 -0
- package/client/src/pages/files/index.tsx +1 -0
- package/client/src/pages/files/language.ts +18 -0
- package/client/src/pages/files/tree-node.tsx +69 -0
- package/client/src/pages/files/use-hljs-theme.ts +23 -0
- package/client/src/pages/logs.tsx +77 -22
- package/client/src/style.css +144 -0
- package/client/src/utils/parseComposerContent.ts +57 -0
- package/client/tailwind.config.js +1 -0
- package/client/tsconfig.json +3 -1
- package/dist/assets/index-COKXlFo2.js +124 -0
- package/dist/assets/style-kkLO-vsa.css +3 -0
- package/dist/client.js +4262 -1
- package/dist/index.html +2 -2
- package/dist/radix-ui.js +1261 -1262
- package/dist/react-dom-client.js +2243 -2240
- package/dist/react-dom.js +15 -15
- package/dist/style.css +1 -3
- package/lib/index.js +1010 -81
- package/lib/transform.js +16 -2
- package/lib/websocket.js +845 -28
- package/node.tsconfig.json +18 -0
- package/package.json +15 -16
- package/src/bin.ts +24 -0
- package/src/bot-db-models.ts +74 -0
- package/src/bot-hub.ts +240 -0
- package/src/bot-persistence.ts +270 -0
- package/src/build.ts +90 -0
- package/src/dev.ts +107 -0
- package/src/index.ts +337 -0
- package/src/transform.ts +199 -0
- package/src/websocket.ts +1369 -0
- package/client/src/pages/database.tsx +0 -708
- package/client/src/pages/files.tsx +0 -470
- package/client/src/pages/login-assist.tsx +0 -225
- 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
|
|
4
|
-
import
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
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("..") ||
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
248
|
-
const content =
|
|
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 =
|
|
266
|
-
|
|
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 =
|
|
292
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
323
|
-
const dir =
|
|
324
|
-
if (!
|
|
325
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
636
|
-
if (!
|
|
637
|
-
const stat =
|
|
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 =
|
|
657
|
-
if (!
|
|
658
|
-
const entries =
|
|
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 =
|
|
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 =
|
|
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))
|
|
718
|
-
|
|
719
|
-
|
|
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 (
|
|
1675
|
+
if (fs2.existsSync(target + ext)) {
|
|
722
1676
|
return specifier + ext;
|
|
723
1677
|
}
|
|
724
1678
|
}
|
|
725
|
-
if (
|
|
1679
|
+
if (fs2.existsSync(target) && fs2.statSync(target).isDirectory()) {
|
|
726
1680
|
for (const ext of RESOLVE_EXTS) {
|
|
727
|
-
if (
|
|
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 =
|
|
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 =
|
|
753
|
-
const ext =
|
|
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
|
-
|
|
792
|
-
const distDir =
|
|
1746
|
+
path2.join(import.meta.dirname, "../client");
|
|
1747
|
+
const distDir = path2.join(import.meta.dirname, "../dist");
|
|
793
1748
|
const resolveFile = (name) => {
|
|
794
|
-
const distPath =
|
|
795
|
-
if (
|
|
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 =
|
|
804
|
-
const filename =
|
|
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 =
|
|
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 =
|
|
1823
|
+
ctx.type = path2.extname(filename);
|
|
895
1824
|
ctx.type = mime.getType(filename) || ctx.type;
|
|
896
|
-
return ctx.body =
|
|
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 =
|
|
913
|
-
const safePfx = baseDir.endsWith(
|
|
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 (
|
|
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 =
|
|
1856
|
+
const fileState = fs2.statSync(resolved);
|
|
928
1857
|
if (fileState.isFile() && !fileState.isSocket() && !fileState.isFIFO()) {
|
|
929
1858
|
return sendFile(resolved);
|
|
930
1859
|
}
|