@zhin.js/console 1.0.51 → 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 +13 -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 +482 -464
- package/dist/index.html +2 -2
- package/dist/style.css +1 -1
- 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 +13 -15
- 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/assets/index-DS4RbHWX.js +0 -124
- package/dist/assets/style-DS-m6WEr.css +0 -3
package/lib/websocket.js
CHANGED
|
@@ -1,10 +1,334 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs2 from 'fs';
|
|
2
|
+
import path2 from 'path';
|
|
3
3
|
import WebSocket from 'ws';
|
|
4
|
-
import { usePlugin } from '@zhin.js/core';
|
|
4
|
+
import { usePlugin, Adapter } from '@zhin.js/core';
|
|
5
|
+
|
|
6
|
+
// src/websocket.ts
|
|
7
|
+
|
|
8
|
+
// src/bot-persistence.ts
|
|
9
|
+
var DATA_DIR = path2.join(process.cwd(), "data");
|
|
10
|
+
var REQ_FILE = path2.join(DATA_DIR, "console_bot_requests.json");
|
|
11
|
+
var NOTICE_FILE = path2.join(DATA_DIR, "console_bot_notices.json");
|
|
12
|
+
function ensureDir() {
|
|
13
|
+
if (!fs2.existsSync(DATA_DIR)) fs2.mkdirSync(DATA_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
function loadFile(file, empty) {
|
|
16
|
+
try {
|
|
17
|
+
if (!fs2.existsSync(file)) return { ...empty };
|
|
18
|
+
const raw = fs2.readFileSync(file, "utf-8");
|
|
19
|
+
const j = JSON.parse(raw);
|
|
20
|
+
if (!j || !Array.isArray(j.rows)) return { ...empty };
|
|
21
|
+
return {
|
|
22
|
+
nextId: typeof j.nextId === "number" ? j.nextId : 1,
|
|
23
|
+
rows: j.rows
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return { ...empty };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function saveFile(file, store) {
|
|
30
|
+
ensureDir();
|
|
31
|
+
fs2.writeFileSync(file, JSON.stringify(store, null, 0), "utf-8");
|
|
32
|
+
}
|
|
33
|
+
async function insertRequest(row) {
|
|
34
|
+
const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
|
|
35
|
+
const id = store.nextId++;
|
|
36
|
+
const full = { ...row, id, consumed: 0 };
|
|
37
|
+
store.rows.push(full);
|
|
38
|
+
saveFile(REQ_FILE, store);
|
|
39
|
+
return full;
|
|
40
|
+
}
|
|
41
|
+
async function insertNotice(row) {
|
|
42
|
+
const store = loadFile(NOTICE_FILE, { nextId: 1, rows: [] });
|
|
43
|
+
const id = store.nextId++;
|
|
44
|
+
const full = { ...row, id, consumed: 0 };
|
|
45
|
+
store.rows.push(full);
|
|
46
|
+
saveFile(NOTICE_FILE, store);
|
|
47
|
+
return full;
|
|
48
|
+
}
|
|
49
|
+
async function listUnconsumedRequests() {
|
|
50
|
+
const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
|
|
51
|
+
return store.rows.filter((r) => r.consumed === 0).sort((a, b) => a.created_at - b.created_at);
|
|
52
|
+
}
|
|
53
|
+
async function listUnconsumedNotices() {
|
|
54
|
+
const store = loadFile(NOTICE_FILE, { nextId: 1, rows: [] });
|
|
55
|
+
return store.rows.filter((r) => r.consumed === 0).sort((a, b) => a.created_at - b.created_at);
|
|
56
|
+
}
|
|
57
|
+
async function listRequestsForBot(adapter, botId) {
|
|
58
|
+
const all = await listUnconsumedRequests();
|
|
59
|
+
return all.filter((r) => r.adapter === adapter && r.bot_id === botId);
|
|
60
|
+
}
|
|
61
|
+
async function markRequestsConsumed(ids) {
|
|
62
|
+
if (!ids.length) return;
|
|
63
|
+
const now = Date.now();
|
|
64
|
+
const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
|
|
65
|
+
const set = new Set(ids);
|
|
66
|
+
for (const r of store.rows) {
|
|
67
|
+
if (set.has(r.id) && r.consumed === 0) {
|
|
68
|
+
r.consumed = 1;
|
|
69
|
+
r.consumed_at = now;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
saveFile(REQ_FILE, store);
|
|
73
|
+
}
|
|
74
|
+
async function markNoticesConsumed(ids) {
|
|
75
|
+
if (!ids.length) return;
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const store = loadFile(NOTICE_FILE, { nextId: 1, rows: [] });
|
|
78
|
+
const set = new Set(ids);
|
|
79
|
+
for (const r of store.rows) {
|
|
80
|
+
if (set.has(r.id) && r.consumed === 0) {
|
|
81
|
+
r.consumed = 1;
|
|
82
|
+
r.consumed_at = now;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
saveFile(NOTICE_FILE, store);
|
|
86
|
+
}
|
|
87
|
+
async function findRequestRow(adapter, botId, platformRequestId) {
|
|
88
|
+
const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
|
|
89
|
+
const r = store.rows.find(
|
|
90
|
+
(x) => x.adapter === adapter && x.bot_id === botId && x.platform_request_id === platformRequestId && x.consumed === 0
|
|
91
|
+
);
|
|
92
|
+
return r;
|
|
93
|
+
}
|
|
94
|
+
async function getRequestRowById(id) {
|
|
95
|
+
const store = loadFile(REQ_FILE, { nextId: 1, rows: [] });
|
|
96
|
+
const r = store.rows.find((x) => x.id === id);
|
|
97
|
+
return r;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/bot-hub.ts
|
|
101
|
+
var wssRef = null;
|
|
102
|
+
var hubInited = false;
|
|
103
|
+
function setBotHubWss(wss) {
|
|
104
|
+
wssRef = wss;
|
|
105
|
+
}
|
|
106
|
+
function broadcast(obj) {
|
|
107
|
+
const msg = JSON.stringify(obj);
|
|
108
|
+
const clients = wssRef?.clients;
|
|
109
|
+
if (!clients) return;
|
|
110
|
+
const list = clients instanceof Set ? [...clients] : clients;
|
|
111
|
+
for (const ws of list) {
|
|
112
|
+
if (ws.readyState === 1) ws.send(msg);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function requestMemoryKey(adapter, botId, platformId) {
|
|
116
|
+
return `${adapter}:${botId}:${platformId}`;
|
|
117
|
+
}
|
|
118
|
+
var pendingRequestObjects = /* @__PURE__ */ new Map();
|
|
119
|
+
async function storePendingRequest(adapter, botId, req) {
|
|
120
|
+
const platformId = req.$id;
|
|
121
|
+
const key = requestMemoryKey(adapter, botId, platformId);
|
|
122
|
+
pendingRequestObjects.set(key, req);
|
|
123
|
+
const row = await insertRequest({
|
|
124
|
+
adapter,
|
|
125
|
+
bot_id: botId,
|
|
126
|
+
platform_request_id: platformId,
|
|
127
|
+
type: String(req.$type),
|
|
128
|
+
sender_id: String(req.$sender?.id ?? ""),
|
|
129
|
+
sender_name: String(req.$sender?.name ?? ""),
|
|
130
|
+
comment: String(req.$comment ?? ""),
|
|
131
|
+
channel_id: String(req.$channel?.id ?? ""),
|
|
132
|
+
channel_type: String(req.$channel?.type ?? "private"),
|
|
133
|
+
created_at: typeof req.$timestamp === "number" ? req.$timestamp : Date.now()
|
|
134
|
+
});
|
|
135
|
+
return row;
|
|
136
|
+
}
|
|
137
|
+
function rowToRequestPushData(row, canAct) {
|
|
138
|
+
return {
|
|
139
|
+
id: row.id,
|
|
140
|
+
adapter: row.adapter,
|
|
141
|
+
botId: row.bot_id,
|
|
142
|
+
platformRequestId: row.platform_request_id,
|
|
143
|
+
type: row.type,
|
|
144
|
+
sender: { id: row.sender_id, name: row.sender_name },
|
|
145
|
+
comment: row.comment,
|
|
146
|
+
channel: { id: row.channel_id, type: row.channel_type },
|
|
147
|
+
timestamp: row.created_at,
|
|
148
|
+
canAct
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function onRequestReceived(adapter, botId, req) {
|
|
152
|
+
const row = await storePendingRequest(adapter, botId, req);
|
|
153
|
+
const key = requestMemoryKey(adapter, botId, req.$id);
|
|
154
|
+
const canAct = pendingRequestObjects.has(key);
|
|
155
|
+
broadcast({ type: "bot:request", data: rowToRequestPushData(row, canAct) });
|
|
156
|
+
}
|
|
157
|
+
async function onNoticeReceived(adapter, botId, notice) {
|
|
158
|
+
let raw = {};
|
|
159
|
+
try {
|
|
160
|
+
raw = Object.fromEntries(
|
|
161
|
+
Object.entries(notice).filter(
|
|
162
|
+
([k]) => !k.startsWith("$") && k !== "adapter" && k !== "bot"
|
|
163
|
+
)
|
|
164
|
+
);
|
|
165
|
+
} catch {
|
|
166
|
+
raw = {};
|
|
167
|
+
}
|
|
168
|
+
let payload;
|
|
169
|
+
try {
|
|
170
|
+
payload = JSON.stringify({
|
|
171
|
+
type: notice.$type,
|
|
172
|
+
subType: notice.$subType,
|
|
173
|
+
channel: notice.$channel,
|
|
174
|
+
raw
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
payload = JSON.stringify({ type: notice.$type, error: "serialize_failed" });
|
|
178
|
+
}
|
|
179
|
+
const row = await insertNotice({
|
|
180
|
+
adapter,
|
|
181
|
+
bot_id: botId,
|
|
182
|
+
notice_type: String(notice.$type ?? "unknown"),
|
|
183
|
+
channel_type: String(notice.$channel?.type ?? ""),
|
|
184
|
+
channel_id: String(notice.$channel?.id ?? ""),
|
|
185
|
+
payload,
|
|
186
|
+
created_at: typeof notice.$timestamp === "number" ? notice.$timestamp : Date.now()
|
|
187
|
+
});
|
|
188
|
+
broadcast({
|
|
189
|
+
type: "bot:notice",
|
|
190
|
+
data: {
|
|
191
|
+
id: row.id,
|
|
192
|
+
adapter: row.adapter,
|
|
193
|
+
botId: row.bot_id,
|
|
194
|
+
noticeType: row.notice_type,
|
|
195
|
+
channel: { id: row.channel_id, type: row.channel_type },
|
|
196
|
+
payload: row.payload,
|
|
197
|
+
timestamp: row.created_at
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function getPendingRequest(adapter, botId, platformRequestId) {
|
|
202
|
+
return pendingRequestObjects.get(requestMemoryKey(adapter, botId, platformRequestId));
|
|
203
|
+
}
|
|
204
|
+
function removePendingRequest(adapter, botId, platformRequestId) {
|
|
205
|
+
pendingRequestObjects.delete(requestMemoryKey(adapter, botId, platformRequestId));
|
|
206
|
+
}
|
|
207
|
+
async function markRequestConsumedByPlatformId(adapter, botId, platformRequestId) {
|
|
208
|
+
const row = await findRequestRow(adapter, botId, platformRequestId);
|
|
209
|
+
if (row) await markRequestsConsumed([row.id]);
|
|
210
|
+
removePendingRequest(adapter, botId, platformRequestId);
|
|
211
|
+
}
|
|
212
|
+
async function sendCatchUpToClient(ws) {
|
|
213
|
+
const reqs = await listUnconsumedRequests();
|
|
214
|
+
for (const row of reqs) {
|
|
215
|
+
const canAct = pendingRequestObjects.has(
|
|
216
|
+
requestMemoryKey(row.adapter, row.bot_id, row.platform_request_id)
|
|
217
|
+
);
|
|
218
|
+
if (ws.readyState === 1) {
|
|
219
|
+
ws.send(
|
|
220
|
+
JSON.stringify({
|
|
221
|
+
type: "bot:request",
|
|
222
|
+
data: rowToRequestPushData(row, canAct)
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const notices = await listUnconsumedNotices();
|
|
228
|
+
for (const row of notices) {
|
|
229
|
+
if (ws.readyState === 1) {
|
|
230
|
+
ws.send(
|
|
231
|
+
JSON.stringify({
|
|
232
|
+
type: "bot:notice",
|
|
233
|
+
data: {
|
|
234
|
+
id: row.id,
|
|
235
|
+
adapter: row.adapter,
|
|
236
|
+
botId: row.bot_id,
|
|
237
|
+
noticeType: row.notice_type,
|
|
238
|
+
channel: { id: row.channel_id, type: row.channel_type },
|
|
239
|
+
payload: row.payload,
|
|
240
|
+
timestamp: row.created_at
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function initBotHub(root2) {
|
|
248
|
+
if (hubInited) return;
|
|
249
|
+
hubInited = true;
|
|
250
|
+
const handlerReq = (req) => {
|
|
251
|
+
const adapter = String(req.$adapter);
|
|
252
|
+
const botId = String(req.$bot);
|
|
253
|
+
onRequestReceived(adapter, botId, req);
|
|
254
|
+
};
|
|
255
|
+
const handlerNotice = (notice) => {
|
|
256
|
+
const adapter = String(notice.$adapter);
|
|
257
|
+
const botId = String(notice.$bot);
|
|
258
|
+
onNoticeReceived(adapter, botId, notice);
|
|
259
|
+
};
|
|
260
|
+
root2.on("request.receive", handlerReq);
|
|
261
|
+
root2.on("notice.receive", handlerNotice);
|
|
262
|
+
const adapterNames = root2.adapters ? [...root2.adapters] : [];
|
|
263
|
+
const inject = root2.inject;
|
|
264
|
+
if (inject && typeof inject === "function" && adapterNames.length > 0) {
|
|
265
|
+
for (const name of adapterNames) {
|
|
266
|
+
try {
|
|
267
|
+
const ad = inject(name);
|
|
268
|
+
if (ad && typeof ad.on === "function") {
|
|
269
|
+
ad.on("message.receive", (msg) => {
|
|
270
|
+
const payload = {
|
|
271
|
+
type: "bot:message",
|
|
272
|
+
data: {
|
|
273
|
+
adapter: name,
|
|
274
|
+
botId: msg?.$bot,
|
|
275
|
+
channelId: msg?.$channel?.id,
|
|
276
|
+
channelType: msg?.$channel?.type,
|
|
277
|
+
sender: msg?.$sender,
|
|
278
|
+
content: msg?.$content ?? [],
|
|
279
|
+
timestamp: typeof msg?.$timestamp === "number" ? msg.$timestamp : Date.now()
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
broadcast(payload);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
5
290
|
|
|
6
291
|
// src/websocket.ts
|
|
7
292
|
var { root, logger } = usePlugin();
|
|
293
|
+
function collectBotsList() {
|
|
294
|
+
const bots = [];
|
|
295
|
+
for (const name of root.adapters) {
|
|
296
|
+
const adapter = root.inject(name);
|
|
297
|
+
if (adapter instanceof Adapter) {
|
|
298
|
+
for (const [botName, bot] of adapter.bots.entries()) {
|
|
299
|
+
bots.push({
|
|
300
|
+
name: botName,
|
|
301
|
+
adapter: String(name),
|
|
302
|
+
connected: !!bot.$connected,
|
|
303
|
+
status: bot.$connected ? "online" : "offline"
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return bots;
|
|
309
|
+
}
|
|
310
|
+
async function collectBotsListWithPending() {
|
|
311
|
+
const bots = collectBotsList();
|
|
312
|
+
let reqs = [];
|
|
313
|
+
let notices = [];
|
|
314
|
+
try {
|
|
315
|
+
[reqs, notices] = await Promise.all([listUnconsumedRequests(), listUnconsumedNotices()]);
|
|
316
|
+
} catch {
|
|
317
|
+
}
|
|
318
|
+
return bots.map((bot) => {
|
|
319
|
+
const pendingRequestCount = reqs.filter(
|
|
320
|
+
(r) => r.adapter === bot.adapter && r.bot_id === bot.name
|
|
321
|
+
).length;
|
|
322
|
+
const pendingNoticeCount = notices.filter(
|
|
323
|
+
(n) => n.adapter === bot.adapter && n.bot_id === bot.name
|
|
324
|
+
).length;
|
|
325
|
+
return {
|
|
326
|
+
...bot,
|
|
327
|
+
pendingRequestCount,
|
|
328
|
+
pendingNoticeCount
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
}
|
|
8
332
|
var ENV_WHITELIST = [".env", ".env.development", ".env.production"];
|
|
9
333
|
var FILE_MANAGER_ALLOWED = [
|
|
10
334
|
"src",
|
|
@@ -28,7 +352,7 @@ var FILE_MANAGER_BLOCKED = /* @__PURE__ */ new Set([
|
|
|
28
352
|
"coverage"
|
|
29
353
|
]);
|
|
30
354
|
function isPathAllowed(relativePath) {
|
|
31
|
-
if (relativePath.includes("..") ||
|
|
355
|
+
if (relativePath.includes("..") || path2.isAbsolute(relativePath)) return false;
|
|
32
356
|
const normalized = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
33
357
|
const firstSegment = normalized.split("/")[0];
|
|
34
358
|
if (FILE_MANAGER_BLOCKED.has(firstSegment)) return false;
|
|
@@ -48,15 +372,20 @@ function getPluginKeys() {
|
|
|
48
372
|
return Array.from(keys);
|
|
49
373
|
}
|
|
50
374
|
function getConfigFilePath() {
|
|
51
|
-
return
|
|
375
|
+
return path2.resolve(process.cwd(), "zhin.config.yml");
|
|
52
376
|
}
|
|
53
377
|
function setupWebSocket(webServer) {
|
|
378
|
+
setBotHubWss(webServer.ws);
|
|
379
|
+
initBotHub(root);
|
|
54
380
|
webServer.ws.on("connection", (ws) => {
|
|
55
381
|
ws.send(JSON.stringify({
|
|
56
382
|
type: "sync",
|
|
57
383
|
data: { key: "entries", value: Object.values(webServer.entries) }
|
|
58
384
|
}));
|
|
59
385
|
ws.send(JSON.stringify({ type: "init-data", timestamp: Date.now() }));
|
|
386
|
+
void sendCatchUpToClient(ws).catch(
|
|
387
|
+
(e) => logger.warn("[console] bot catch-up failed", e.message)
|
|
388
|
+
);
|
|
60
389
|
ws.on("message", async (data) => {
|
|
61
390
|
try {
|
|
62
391
|
const message = JSON.parse(data.toString());
|
|
@@ -88,7 +417,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
88
417
|
case "config:get-yaml":
|
|
89
418
|
try {
|
|
90
419
|
const filePath = getConfigFilePath();
|
|
91
|
-
const yaml =
|
|
420
|
+
const yaml = fs2.existsSync(filePath) ? fs2.readFileSync(filePath, "utf-8") : "";
|
|
92
421
|
ws.send(JSON.stringify({ requestId, data: { yaml, pluginKeys: getPluginKeys() } }));
|
|
93
422
|
} catch (error) {
|
|
94
423
|
ws.send(JSON.stringify({ requestId, error: `Failed to read config: ${error.message}` }));
|
|
@@ -102,7 +431,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
102
431
|
break;
|
|
103
432
|
}
|
|
104
433
|
const filePath = getConfigFilePath();
|
|
105
|
-
|
|
434
|
+
fs2.writeFileSync(filePath, yaml, "utf-8");
|
|
106
435
|
const configService = root.inject("config");
|
|
107
436
|
const loader = configService.configs.get("zhin.config.yml");
|
|
108
437
|
if (loader) loader.load();
|
|
@@ -225,7 +554,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
225
554
|
const cwd = process.cwd();
|
|
226
555
|
const files = ENV_WHITELIST.map((name) => ({
|
|
227
556
|
name,
|
|
228
|
-
exists:
|
|
557
|
+
exists: fs2.existsSync(path2.resolve(cwd, name))
|
|
229
558
|
}));
|
|
230
559
|
ws.send(JSON.stringify({ requestId, data: { files } }));
|
|
231
560
|
} catch (error) {
|
|
@@ -239,8 +568,8 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
239
568
|
ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
|
|
240
569
|
break;
|
|
241
570
|
}
|
|
242
|
-
const envPath =
|
|
243
|
-
const content =
|
|
571
|
+
const envPath = path2.resolve(process.cwd(), filename);
|
|
572
|
+
const content = fs2.existsSync(envPath) ? fs2.readFileSync(envPath, "utf-8") : "";
|
|
244
573
|
ws.send(JSON.stringify({ requestId, data: { content } }));
|
|
245
574
|
} catch (error) {
|
|
246
575
|
ws.send(JSON.stringify({ requestId, error: `Failed to read env file: ${error.message}` }));
|
|
@@ -257,8 +586,8 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
257
586
|
ws.send(JSON.stringify({ requestId, error: "content field is required" }));
|
|
258
587
|
break;
|
|
259
588
|
}
|
|
260
|
-
const envPath =
|
|
261
|
-
|
|
589
|
+
const envPath = path2.resolve(process.cwd(), filename);
|
|
590
|
+
fs2.writeFileSync(envPath, content, "utf-8");
|
|
262
591
|
ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u73AF\u5883\u53D8\u91CF\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
|
|
263
592
|
} catch (error) {
|
|
264
593
|
ws.send(JSON.stringify({ requestId, error: `Failed to save env file: ${error.message}` }));
|
|
@@ -283,12 +612,12 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
283
612
|
ws.send(JSON.stringify({ requestId, error: `Access denied: ${fp}` }));
|
|
284
613
|
break;
|
|
285
614
|
}
|
|
286
|
-
const absPath =
|
|
287
|
-
if (!
|
|
615
|
+
const absPath = path2.resolve(process.cwd(), fp);
|
|
616
|
+
if (!fs2.existsSync(absPath)) {
|
|
288
617
|
ws.send(JSON.stringify({ requestId, error: `File not found: ${fp}` }));
|
|
289
618
|
break;
|
|
290
619
|
}
|
|
291
|
-
const stat =
|
|
620
|
+
const stat = fs2.statSync(absPath);
|
|
292
621
|
if (!stat.isFile()) {
|
|
293
622
|
ws.send(JSON.stringify({ requestId, error: `Not a file: ${fp}` }));
|
|
294
623
|
break;
|
|
@@ -297,7 +626,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
297
626
|
ws.send(JSON.stringify({ requestId, error: `File too large: ${(stat.size / 1024).toFixed(0)}KB (max 1MB)` }));
|
|
298
627
|
break;
|
|
299
628
|
}
|
|
300
|
-
const fileContent =
|
|
629
|
+
const fileContent = fs2.readFileSync(absPath, "utf-8");
|
|
301
630
|
ws.send(JSON.stringify({ requestId, data: { content: fileContent, size: stat.size } }));
|
|
302
631
|
} catch (error) {
|
|
303
632
|
ws.send(JSON.stringify({ requestId, error: `Failed to read file: ${error.message}` }));
|
|
@@ -314,12 +643,12 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
314
643
|
ws.send(JSON.stringify({ requestId, error: "content field is required" }));
|
|
315
644
|
break;
|
|
316
645
|
}
|
|
317
|
-
const absPath =
|
|
318
|
-
const dir =
|
|
319
|
-
if (!
|
|
320
|
-
|
|
646
|
+
const absPath = path2.resolve(process.cwd(), fp);
|
|
647
|
+
const dir = path2.dirname(absPath);
|
|
648
|
+
if (!fs2.existsSync(dir)) {
|
|
649
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
321
650
|
}
|
|
322
|
-
|
|
651
|
+
fs2.writeFileSync(absPath, fileContent, "utf-8");
|
|
323
652
|
ws.send(JSON.stringify({ requestId, data: { success: true, message: `\u6587\u4EF6\u5DF2\u4FDD\u5B58: ${fp}` } }));
|
|
324
653
|
} catch (error) {
|
|
325
654
|
ws.send(JSON.stringify({ requestId, error: `Failed to save file: ${error.message}` }));
|
|
@@ -462,6 +791,494 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
462
791
|
ws.send(JSON.stringify({ requestId, error: `Failed to get entries: ${error.message}` }));
|
|
463
792
|
}
|
|
464
793
|
break;
|
|
794
|
+
// ================================================================
|
|
795
|
+
// 机器人管理(WebSocket)
|
|
796
|
+
// ================================================================
|
|
797
|
+
case "bot:list": {
|
|
798
|
+
try {
|
|
799
|
+
const botsWithPending = await collectBotsListWithPending();
|
|
800
|
+
ws.send(JSON.stringify({ requestId, data: { bots: botsWithPending } }));
|
|
801
|
+
} catch (error) {
|
|
802
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
803
|
+
}
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
case "bot:info": {
|
|
807
|
+
try {
|
|
808
|
+
const d = message.data || {};
|
|
809
|
+
const { adapter, botId } = d;
|
|
810
|
+
if (!adapter || !botId) {
|
|
811
|
+
ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
const ad = root.inject(adapter);
|
|
815
|
+
if (!(ad instanceof Adapter)) {
|
|
816
|
+
ws.send(JSON.stringify({ requestId, error: "adapter not found" }));
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
const bot = ad.bots.get(botId);
|
|
820
|
+
if (!bot) {
|
|
821
|
+
ws.send(JSON.stringify({ requestId, error: "bot not found" }));
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
ws.send(
|
|
825
|
+
JSON.stringify({
|
|
826
|
+
requestId,
|
|
827
|
+
data: {
|
|
828
|
+
name: botId,
|
|
829
|
+
adapter: String(adapter),
|
|
830
|
+
connected: !!bot.$connected,
|
|
831
|
+
status: bot.$connected ? "online" : "offline"
|
|
832
|
+
}
|
|
833
|
+
})
|
|
834
|
+
);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
837
|
+
}
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
case "bot:sendMessage": {
|
|
841
|
+
try {
|
|
842
|
+
const d = message.data || {};
|
|
843
|
+
const { adapter, botId, id, type: msgType, content } = d;
|
|
844
|
+
if (!adapter || !botId || !id || !msgType || content === void 0) {
|
|
845
|
+
ws.send(
|
|
846
|
+
JSON.stringify({
|
|
847
|
+
requestId,
|
|
848
|
+
error: "adapter, botId, id, type, content required"
|
|
849
|
+
})
|
|
850
|
+
);
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
const ad = root.inject(adapter);
|
|
854
|
+
if (!(ad instanceof Adapter)) {
|
|
855
|
+
ws.send(JSON.stringify({ requestId, error: "adapter not found" }));
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
const normalized = typeof content === "string" ? content : Array.isArray(content) ? content : String(content);
|
|
859
|
+
const messageId = await ad.sendMessage({
|
|
860
|
+
context: adapter,
|
|
861
|
+
bot: botId,
|
|
862
|
+
id: String(id),
|
|
863
|
+
type: msgType,
|
|
864
|
+
content: normalized
|
|
865
|
+
});
|
|
866
|
+
ws.send(JSON.stringify({ requestId, data: { messageId } }));
|
|
867
|
+
} catch (error) {
|
|
868
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
869
|
+
}
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
case "bot:friends":
|
|
873
|
+
case "bot:groups": {
|
|
874
|
+
try {
|
|
875
|
+
const d = message.data || {};
|
|
876
|
+
const { adapter, botId } = d;
|
|
877
|
+
if (!adapter || !botId) {
|
|
878
|
+
ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
if (adapter !== "icqq") {
|
|
882
|
+
ws.send(JSON.stringify({ requestId, error: "not supported for this adapter" }));
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
const ad = root.inject("icqq");
|
|
886
|
+
const bot = ad?.bots?.get?.(botId);
|
|
887
|
+
if (!bot) {
|
|
888
|
+
ws.send(JSON.stringify({ requestId, error: "bot not found" }));
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
if (type === "bot:friends") {
|
|
892
|
+
const fl = bot.fl;
|
|
893
|
+
const friends = Array.from((fl || /* @__PURE__ */ new Map()).values()).map((f) => ({
|
|
894
|
+
user_id: f.user_id,
|
|
895
|
+
nickname: f.nickname,
|
|
896
|
+
remark: f.remark
|
|
897
|
+
}));
|
|
898
|
+
ws.send(JSON.stringify({ requestId, data: { friends, count: friends.length } }));
|
|
899
|
+
} else {
|
|
900
|
+
const gl = bot.gl;
|
|
901
|
+
const groups = Array.from((gl || /* @__PURE__ */ new Map()).values()).map((g) => ({
|
|
902
|
+
group_id: g.group_id,
|
|
903
|
+
name: g.name
|
|
904
|
+
}));
|
|
905
|
+
ws.send(JSON.stringify({ requestId, data: { groups, count: groups.length } }));
|
|
906
|
+
}
|
|
907
|
+
} catch (error) {
|
|
908
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
909
|
+
}
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
case "bot:channels": {
|
|
913
|
+
try {
|
|
914
|
+
const d = message.data || {};
|
|
915
|
+
const { adapter, botId } = d;
|
|
916
|
+
if (!adapter || !botId) {
|
|
917
|
+
ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
if (adapter === "icqq") {
|
|
921
|
+
ws.send(JSON.stringify({ requestId, error: "channels not supported for icqq" }));
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
const ad = root.inject(adapter);
|
|
925
|
+
const bot = ad?.bots?.get?.(botId);
|
|
926
|
+
if (!bot) {
|
|
927
|
+
ws.send(JSON.stringify({ requestId, error: "bot not found" }));
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
const channels = [];
|
|
931
|
+
if (adapter === "qq" && typeof bot.getGuilds === "function" && typeof bot.getChannels === "function") {
|
|
932
|
+
const guilds = await bot.getGuilds() || [];
|
|
933
|
+
for (const g of guilds) {
|
|
934
|
+
const gid = g?.id ?? g?.guild_id ?? String(g);
|
|
935
|
+
const chs = await bot.getChannels(gid) || [];
|
|
936
|
+
for (const c of chs) {
|
|
937
|
+
channels.push({
|
|
938
|
+
id: String(c?.id ?? c?.channel_id ?? c),
|
|
939
|
+
name: String(c?.name ?? c?.channel_name ?? c?.id ?? "")
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
} else if (typeof ad?.listChannels === "function") {
|
|
944
|
+
const result = await ad.listChannels(botId);
|
|
945
|
+
if (Array.isArray(result)) channels.push(...result.map((c) => ({ id: String(c?.id ?? c), name: String(c?.name ?? c?.id ?? "") })));
|
|
946
|
+
else if (result?.channels) channels.push(...result.channels.map((c) => ({ id: String(c?.id ?? c), name: String(c?.name ?? c?.id ?? "") })));
|
|
947
|
+
}
|
|
948
|
+
ws.send(JSON.stringify({ requestId, data: { channels, count: channels.length } }));
|
|
949
|
+
} catch (error) {
|
|
950
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
951
|
+
}
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
case "bot:deleteFriend": {
|
|
955
|
+
try {
|
|
956
|
+
const d = message.data || {};
|
|
957
|
+
const { adapter, botId, userId } = d;
|
|
958
|
+
if (!adapter || !botId || !userId) {
|
|
959
|
+
ws.send(JSON.stringify({ requestId, error: "adapter, botId, userId required" }));
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
const ad = root.inject(adapter);
|
|
963
|
+
const bot = ad?.bots?.get?.(botId);
|
|
964
|
+
if (!bot) {
|
|
965
|
+
ws.send(JSON.stringify({ requestId, error: "bot not found" }));
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
if (adapter === "icqq" && typeof bot.deleteFriend === "function") {
|
|
969
|
+
await bot.deleteFriend(Number(userId));
|
|
970
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
971
|
+
} else if (adapter === "icqq" && typeof bot.delete_friend === "function") {
|
|
972
|
+
await bot.delete_friend(Number(userId));
|
|
973
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
974
|
+
} else {
|
|
975
|
+
ws.send(JSON.stringify({ requestId, error: "\u5F53\u524D\u9002\u914D\u5668\u6682\u4E0D\u652F\u6301\u5220\u9664\u597D\u53CB" }));
|
|
976
|
+
}
|
|
977
|
+
} catch (error) {
|
|
978
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
979
|
+
}
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
case "bot:requests": {
|
|
983
|
+
try {
|
|
984
|
+
const d = message.data || {};
|
|
985
|
+
const { adapter, botId } = d;
|
|
986
|
+
if (!adapter || !botId) {
|
|
987
|
+
ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
const rows = await listRequestsForBot(String(adapter), String(botId));
|
|
991
|
+
ws.send(
|
|
992
|
+
JSON.stringify({
|
|
993
|
+
requestId,
|
|
994
|
+
data: {
|
|
995
|
+
requests: rows.map((r) => ({
|
|
996
|
+
id: r.id,
|
|
997
|
+
platformRequestId: r.platform_request_id,
|
|
998
|
+
type: r.type,
|
|
999
|
+
sender: { id: r.sender_id, name: r.sender_name },
|
|
1000
|
+
comment: r.comment,
|
|
1001
|
+
channel: { id: r.channel_id, type: r.channel_type },
|
|
1002
|
+
timestamp: r.created_at
|
|
1003
|
+
}))
|
|
1004
|
+
}
|
|
1005
|
+
})
|
|
1006
|
+
);
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1009
|
+
}
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
case "bot:requestApprove":
|
|
1013
|
+
case "bot:requestReject": {
|
|
1014
|
+
try {
|
|
1015
|
+
const d = message.data || {};
|
|
1016
|
+
const { adapter, botId, requestId: platformReqId, remark, reason } = d;
|
|
1017
|
+
if (!adapter || !botId || !platformReqId) {
|
|
1018
|
+
ws.send(
|
|
1019
|
+
JSON.stringify({
|
|
1020
|
+
requestId,
|
|
1021
|
+
error: "adapter, botId, requestId required"
|
|
1022
|
+
})
|
|
1023
|
+
);
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
const req = getPendingRequest(String(adapter), String(botId), String(platformReqId));
|
|
1027
|
+
if (!req) {
|
|
1028
|
+
ws.send(
|
|
1029
|
+
JSON.stringify({
|
|
1030
|
+
requestId,
|
|
1031
|
+
error: "request not in memory (restart?) \u2014 use bot:requestConsumed to dismiss"
|
|
1032
|
+
})
|
|
1033
|
+
);
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
if (type === "bot:requestApprove") await req.$approve(remark);
|
|
1037
|
+
else await req.$reject(reason);
|
|
1038
|
+
await markRequestConsumedByPlatformId(String(adapter), String(botId), String(platformReqId));
|
|
1039
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1042
|
+
}
|
|
1043
|
+
break;
|
|
1044
|
+
}
|
|
1045
|
+
case "bot:requestConsumed": {
|
|
1046
|
+
try {
|
|
1047
|
+
const d = message.data || {};
|
|
1048
|
+
const ids = d.ids ?? (d.id != null ? [d.id] : []);
|
|
1049
|
+
if (!Array.isArray(ids) || !ids.length) {
|
|
1050
|
+
ws.send(JSON.stringify({ requestId, error: "id or ids required" }));
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
const numIds = ids.map(Number);
|
|
1054
|
+
for (const id of numIds) {
|
|
1055
|
+
const row = await getRequestRowById(id);
|
|
1056
|
+
if (row && row.consumed === 0) {
|
|
1057
|
+
removePendingRequest(row.adapter, row.bot_id, row.platform_request_id);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
await markRequestsConsumed(numIds);
|
|
1061
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1064
|
+
}
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
case "bot:noticeConsumed": {
|
|
1068
|
+
try {
|
|
1069
|
+
const d = message.data || {};
|
|
1070
|
+
const ids = d.ids ?? (d.id != null ? [d.id] : []);
|
|
1071
|
+
if (!Array.isArray(ids) || !ids.length) {
|
|
1072
|
+
ws.send(JSON.stringify({ requestId, error: "id or ids required" }));
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
await markNoticesConsumed(ids.map(Number));
|
|
1076
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1079
|
+
}
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
case "bot:inboxMessages": {
|
|
1083
|
+
try {
|
|
1084
|
+
const d = message.data || {};
|
|
1085
|
+
const { adapter, botId, channelId, channelType, limit = 50, beforeId, beforeTs } = d;
|
|
1086
|
+
if (!adapter || !botId || !channelId || !channelType) {
|
|
1087
|
+
ws.send(JSON.stringify({ requestId, error: "adapter, botId, channelId, channelType required" }));
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
let db;
|
|
1091
|
+
try {
|
|
1092
|
+
db = root.inject("database");
|
|
1093
|
+
} catch {
|
|
1094
|
+
ws.send(JSON.stringify({ requestId, data: { messages: [], inboxEnabled: false } }));
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
const MessageModel = db?.models?.get("unified_inbox_message");
|
|
1098
|
+
if (!MessageModel) {
|
|
1099
|
+
ws.send(JSON.stringify({ requestId, data: { messages: [], inboxEnabled: false } }));
|
|
1100
|
+
break;
|
|
1101
|
+
}
|
|
1102
|
+
const where = {
|
|
1103
|
+
adapter: String(adapter),
|
|
1104
|
+
bot_id: String(botId),
|
|
1105
|
+
channel_id: String(channelId),
|
|
1106
|
+
channel_type: String(channelType)
|
|
1107
|
+
};
|
|
1108
|
+
if (beforeTs != null) where.created_at = { $lt: Number(beforeTs) };
|
|
1109
|
+
if (beforeId != null) where.id = { $lt: Number(beforeId) };
|
|
1110
|
+
let q = MessageModel.select().where(where).orderBy("created_at", "DESC").limit(Math.min(Number(limit) || 50, 100));
|
|
1111
|
+
const rows = await (typeof q.then === "function" ? q : Promise.resolve(q));
|
|
1112
|
+
const messages = (rows || []).map((r) => ({
|
|
1113
|
+
id: r.id,
|
|
1114
|
+
platform_message_id: r.platform_message_id,
|
|
1115
|
+
sender_id: r.sender_id,
|
|
1116
|
+
sender_name: r.sender_name,
|
|
1117
|
+
content: r.content,
|
|
1118
|
+
raw: r.raw,
|
|
1119
|
+
created_at: r.created_at
|
|
1120
|
+
}));
|
|
1121
|
+
ws.send(JSON.stringify({ requestId, data: { messages, inboxEnabled: true } }));
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1124
|
+
}
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
case "bot:inboxRequests": {
|
|
1128
|
+
try {
|
|
1129
|
+
const d = message.data || {};
|
|
1130
|
+
const { adapter, botId, limit = 30, offset = 0 } = d;
|
|
1131
|
+
if (!adapter || !botId) {
|
|
1132
|
+
ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
let db;
|
|
1136
|
+
try {
|
|
1137
|
+
db = root.inject("database");
|
|
1138
|
+
} catch {
|
|
1139
|
+
ws.send(JSON.stringify({ requestId, data: { requests: [], inboxEnabled: false } }));
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
const RequestModel = db?.models?.get("unified_inbox_request");
|
|
1143
|
+
if (!RequestModel) {
|
|
1144
|
+
ws.send(JSON.stringify({ requestId, data: { requests: [], inboxEnabled: false } }));
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
const where = { adapter: String(adapter), bot_id: String(botId) };
|
|
1148
|
+
const limitNum = Math.min(Number(limit) || 30, 100);
|
|
1149
|
+
const offsetNum = Math.max(0, Number(offset) || 0);
|
|
1150
|
+
let q = RequestModel.select().where(where).orderBy("created_at", "DESC").limit(limitNum).offset(offsetNum);
|
|
1151
|
+
const rows = await (typeof q.then === "function" ? q : Promise.resolve(q));
|
|
1152
|
+
const requests = (rows || []).map((r) => ({
|
|
1153
|
+
id: r.id,
|
|
1154
|
+
platform_request_id: r.platform_request_id,
|
|
1155
|
+
type: r.type,
|
|
1156
|
+
sub_type: r.sub_type,
|
|
1157
|
+
channel_id: r.channel_id,
|
|
1158
|
+
channel_type: r.channel_type,
|
|
1159
|
+
sender_id: r.sender_id,
|
|
1160
|
+
sender_name: r.sender_name,
|
|
1161
|
+
comment: r.comment,
|
|
1162
|
+
created_at: r.created_at,
|
|
1163
|
+
resolved: r.resolved,
|
|
1164
|
+
resolved_at: r.resolved_at
|
|
1165
|
+
}));
|
|
1166
|
+
ws.send(JSON.stringify({ requestId, data: { requests, inboxEnabled: true } }));
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1169
|
+
}
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
case "bot:inboxNotices": {
|
|
1173
|
+
try {
|
|
1174
|
+
const d = message.data || {};
|
|
1175
|
+
const { adapter, botId, limit = 30, offset = 0 } = d;
|
|
1176
|
+
if (!adapter || !botId) {
|
|
1177
|
+
ws.send(JSON.stringify({ requestId, error: "adapter and botId required" }));
|
|
1178
|
+
break;
|
|
1179
|
+
}
|
|
1180
|
+
let db;
|
|
1181
|
+
try {
|
|
1182
|
+
db = root.inject("database");
|
|
1183
|
+
} catch {
|
|
1184
|
+
ws.send(JSON.stringify({ requestId, data: { notices: [], inboxEnabled: false } }));
|
|
1185
|
+
break;
|
|
1186
|
+
}
|
|
1187
|
+
const NoticeModel = db?.models?.get("unified_inbox_notice");
|
|
1188
|
+
if (!NoticeModel) {
|
|
1189
|
+
ws.send(JSON.stringify({ requestId, data: { notices: [], inboxEnabled: false } }));
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
const where = { adapter: String(adapter), bot_id: String(botId) };
|
|
1193
|
+
const limitNum = Math.min(Number(limit) || 30, 100);
|
|
1194
|
+
const offsetNum = Math.max(0, Number(offset) || 0);
|
|
1195
|
+
let q = NoticeModel.select().where(where).orderBy("created_at", "DESC").limit(limitNum).offset(offsetNum);
|
|
1196
|
+
const rows = await (typeof q.then === "function" ? q : Promise.resolve(q));
|
|
1197
|
+
const notices = (rows || []).map((r) => ({
|
|
1198
|
+
id: r.id,
|
|
1199
|
+
platform_notice_id: r.platform_notice_id,
|
|
1200
|
+
type: r.type,
|
|
1201
|
+
sub_type: r.sub_type,
|
|
1202
|
+
channel_id: r.channel_id,
|
|
1203
|
+
channel_type: r.channel_type,
|
|
1204
|
+
operator_id: r.operator_id,
|
|
1205
|
+
operator_name: r.operator_name,
|
|
1206
|
+
target_id: r.target_id,
|
|
1207
|
+
target_name: r.target_name,
|
|
1208
|
+
payload: r.payload,
|
|
1209
|
+
created_at: r.created_at
|
|
1210
|
+
}));
|
|
1211
|
+
ws.send(JSON.stringify({ requestId, data: { notices, inboxEnabled: true } }));
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1214
|
+
}
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
case "bot:groupMembers":
|
|
1218
|
+
case "bot:groupKick":
|
|
1219
|
+
case "bot:groupMute":
|
|
1220
|
+
case "bot:groupAdmin": {
|
|
1221
|
+
try {
|
|
1222
|
+
const d = message.data || {};
|
|
1223
|
+
const { adapter, botId, groupId, userId, duration, enable } = d;
|
|
1224
|
+
if (!adapter || !botId || !groupId) {
|
|
1225
|
+
ws.send(
|
|
1226
|
+
JSON.stringify({ requestId, error: "adapter, botId, groupId required" })
|
|
1227
|
+
);
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
const ad = root.inject(adapter);
|
|
1231
|
+
if (!ad) {
|
|
1232
|
+
ws.send(JSON.stringify({ requestId, error: "adapter not found" }));
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
const gid = String(groupId);
|
|
1236
|
+
if (type === "bot:groupMembers") {
|
|
1237
|
+
if (typeof ad.listMembers !== "function") {
|
|
1238
|
+
ws.send(JSON.stringify({ requestId, error: "adapter does not support listMembers" }));
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
const r = await ad.listMembers(botId, gid);
|
|
1242
|
+
ws.send(JSON.stringify({ requestId, data: r }));
|
|
1243
|
+
} else if (type === "bot:groupKick") {
|
|
1244
|
+
if (!userId) {
|
|
1245
|
+
ws.send(JSON.stringify({ requestId, error: "userId required" }));
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
if (typeof ad.kickMember !== "function") {
|
|
1249
|
+
ws.send(JSON.stringify({ requestId, error: "adapter does not support kickMember" }));
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
await ad.kickMember(botId, gid, String(userId));
|
|
1253
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
1254
|
+
} else if (type === "bot:groupMute") {
|
|
1255
|
+
if (!userId) {
|
|
1256
|
+
ws.send(JSON.stringify({ requestId, error: "userId required" }));
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
if (typeof ad.muteMember !== "function") {
|
|
1260
|
+
ws.send(JSON.stringify({ requestId, error: "adapter does not support muteMember" }));
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1263
|
+
await ad.muteMember(botId, gid, String(userId), duration ?? 600);
|
|
1264
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
1265
|
+
} else {
|
|
1266
|
+
if (!userId) {
|
|
1267
|
+
ws.send(JSON.stringify({ requestId, error: "userId required" }));
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
if (typeof ad.setAdmin !== "function") {
|
|
1271
|
+
ws.send(JSON.stringify({ requestId, error: "adapter does not support setAdmin" }));
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
await ad.setAdmin(botId, gid, String(userId), enable !== false);
|
|
1275
|
+
ws.send(JSON.stringify({ requestId, data: { success: true } }));
|
|
1276
|
+
}
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
ws.send(JSON.stringify({ requestId, error: error.message }));
|
|
1279
|
+
}
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
465
1282
|
default:
|
|
466
1283
|
ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
|
|
467
1284
|
}
|
|
@@ -623,13 +1440,13 @@ async function kvGetEntries(table) {
|
|
|
623
1440
|
}
|
|
624
1441
|
function buildFileTree(cwd, relativePath, allowed) {
|
|
625
1442
|
const tree = [];
|
|
626
|
-
|
|
1443
|
+
path2.resolve(cwd, relativePath);
|
|
627
1444
|
for (const entry of allowed) {
|
|
628
1445
|
const entryRelative = entry;
|
|
629
1446
|
if (entryRelative.includes("/")) continue;
|
|
630
|
-
const absPath =
|
|
631
|
-
if (!
|
|
632
|
-
const stat =
|
|
1447
|
+
const absPath = path2.resolve(cwd, entry);
|
|
1448
|
+
if (!fs2.existsSync(absPath)) continue;
|
|
1449
|
+
const stat = fs2.statSync(absPath);
|
|
633
1450
|
if (stat.isDirectory()) {
|
|
634
1451
|
tree.push({
|
|
635
1452
|
name: entryRelative,
|
|
@@ -648,9 +1465,9 @@ function buildFileTree(cwd, relativePath, allowed) {
|
|
|
648
1465
|
}
|
|
649
1466
|
function buildDirectoryTree(cwd, relativePath, maxDepth) {
|
|
650
1467
|
if (maxDepth <= 0) return [];
|
|
651
|
-
const absDir =
|
|
652
|
-
if (!
|
|
653
|
-
const entries =
|
|
1468
|
+
const absDir = path2.resolve(cwd, relativePath);
|
|
1469
|
+
if (!fs2.existsSync(absDir) || !fs2.statSync(absDir).isDirectory()) return [];
|
|
1470
|
+
const entries = fs2.readdirSync(absDir, { withFileTypes: true });
|
|
654
1471
|
const result = [];
|
|
655
1472
|
for (const entry of entries) {
|
|
656
1473
|
if (FILE_MANAGER_BLOCKED.has(entry.name) || entry.name.startsWith(".")) continue;
|