mioku-service-webui 4.6.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth.ts +68 -1
- package/config-page-loader.ts +9 -1
- package/datasources.ts +1 -1
- package/index.ts +14 -2
- package/package.json +4 -5
- package/port-utils.ts +1 -1
- package/routes/access-control.ts +2 -2
- package/routes/adapter-config.ts +83 -0
- package/routes/ai.ts +59 -3
- package/routes/config.ts +2 -2
- package/routes/data-management.ts +1 -1
- package/routes/index.ts +1 -0
- package/routes/manage.ts +13 -2
- package/routes/store.ts +21 -10
- package/routes/stranger.ts +17 -18
- package/system.ts +218 -198
- package/types.ts +1 -1
- package/utils.ts +7 -2
package/auth.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
|
+
import { logger } from "mioku";
|
|
2
3
|
import type { Context, Next } from "hono";
|
|
3
4
|
import type { AuthConfig } from "./types";
|
|
4
5
|
import { AUTH_PATH, WEEK_MS, readJsonFile, writeJsonFile } from "./utils";
|
|
@@ -27,13 +28,79 @@ export function ensureAuthConfig(): AuthConfig {
|
|
|
27
28
|
void notifyOwnersAuthTokenRefreshed(
|
|
28
29
|
generated.token,
|
|
29
30
|
generated.expiresAt,
|
|
30
|
-
).catch(() => {
|
|
31
|
+
).catch((err) => {
|
|
32
|
+
logger.warn(
|
|
33
|
+
`[webui] 通知主人密钥更新失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
31
36
|
return generated;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
return existing;
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* 距密钥过期多久时主动刷新(默认 6 小时)。
|
|
44
|
+
* 提前刷新的好处:即便长时间没有 API 请求 / 登录触发,密钥也会轮换并通知主人。
|
|
45
|
+
*/
|
|
46
|
+
const AUTH_REFRESH_LEAD_MS = 6 * 60 * 60 * 1000;
|
|
47
|
+
/** 定时巡检周期(默认 60 秒)。 */
|
|
48
|
+
const AUTH_REFRESH_INTERVAL_MS = 60 * 1000;
|
|
49
|
+
|
|
50
|
+
let authRefreshTimer: NodeJS.Timeout | null = null;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 启动密钥定时巡检。即使没有前端访问 / 没有登录请求,
|
|
54
|
+
* 也会在密钥接近过期时主动刷新并通知主人。
|
|
55
|
+
*/
|
|
56
|
+
export function startAuthRefreshTimer(): void {
|
|
57
|
+
if (authRefreshTimer) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// 启动时立即巡检一次,处理"服务运行时密钥刚好过期"的场景
|
|
61
|
+
try {
|
|
62
|
+
ensureAuthConfig();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
logger.warn(
|
|
65
|
+
`[webui] 启动时巡检密钥失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
authRefreshTimer = setInterval(() => {
|
|
69
|
+
try {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const existing = readJsonFile<AuthConfig | null>(AUTH_PATH, null);
|
|
72
|
+
// 仅在接近过期 / 已过期时才触发刷新逻辑,避免无意义的写文件
|
|
73
|
+
if (
|
|
74
|
+
existing &&
|
|
75
|
+
existing.token &&
|
|
76
|
+
existing.expiresAt &&
|
|
77
|
+
existing.expiresAt - now > AUTH_REFRESH_LEAD_MS
|
|
78
|
+
) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
ensureAuthConfig();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.warn(
|
|
84
|
+
`[webui] 定时巡检密钥失败: ${err instanceof Error ? err.message : String(err)}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}, AUTH_REFRESH_INTERVAL_MS);
|
|
88
|
+
// unref 避免阻塞进程退出
|
|
89
|
+
if (typeof authRefreshTimer.unref === "function") {
|
|
90
|
+
authRefreshTimer.unref();
|
|
91
|
+
}
|
|
92
|
+
logger.info(
|
|
93
|
+
`[webui] 已启动密钥定时巡检(周期 ${AUTH_REFRESH_INTERVAL_MS / 1000}s,提前 ${AUTH_REFRESH_LEAD_MS / 60 / 60 / 1000}h 刷新)`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function stopAuthRefreshTimer(): void {
|
|
98
|
+
if (authRefreshTimer) {
|
|
99
|
+
clearInterval(authRefreshTimer);
|
|
100
|
+
authRefreshTimer = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
37
104
|
export function verifyAuthHeader(c: Context): boolean {
|
|
38
105
|
const auth = c.req.header("authorization") || "";
|
|
39
106
|
const token = auth.replace(/^Bearer\s+/i, "").trim();
|
package/config-page-loader.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
|
-
import { logger } from "
|
|
4
|
+
import { logger } from "mioku";
|
|
5
5
|
|
|
6
6
|
export interface ConfigField {
|
|
7
7
|
key: string;
|
|
@@ -44,6 +44,14 @@ export function loadServiceConfigPage(serviceName: string): ConfigPageManifest |
|
|
|
44
44
|
return loadConfigPage(serviceName, possiblePaths);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export function loadAdapterConfigPage(adapterName: string): ConfigPageManifest | null {
|
|
48
|
+
const possiblePaths = [
|
|
49
|
+
path.join(process.cwd(), "node_modules", `mioku-adapter-${adapterName}`),
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
return loadConfigPage(adapterName, possiblePaths);
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
function loadConfigPage(
|
|
48
56
|
packageName: string,
|
|
49
57
|
possiblePaths: string[],
|
package/datasources.ts
CHANGED
package/index.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { logger } from "
|
|
3
|
+
import { logger } from "mioku";
|
|
4
4
|
import { Hono } from "hono";
|
|
5
5
|
import { serve } from "@hono/node-server";
|
|
6
6
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
7
7
|
import { createNodeWebSocket } from "@hono/node-ws";
|
|
8
8
|
import type { MiokuService } from "mioku";
|
|
9
9
|
import type { WebUISettings } from "./types";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ensureAuthConfig,
|
|
12
|
+
loginWithToken,
|
|
13
|
+
requireAuth,
|
|
14
|
+
startAuthRefreshTimer,
|
|
15
|
+
stopAuthRefreshTimer,
|
|
16
|
+
} from "./auth";
|
|
11
17
|
import {
|
|
12
18
|
ensureWebUIDist,
|
|
13
19
|
getWebUISettings,
|
|
@@ -23,6 +29,7 @@ import {
|
|
|
23
29
|
createDBRoutes,
|
|
24
30
|
createPluginConfigRoutes,
|
|
25
31
|
createServiceConfigRoutes,
|
|
32
|
+
createAdapterConfigRoutes,
|
|
26
33
|
createMemeRoutes,
|
|
27
34
|
createManageRoutes,
|
|
28
35
|
createStoreRoutes,
|
|
@@ -118,6 +125,7 @@ class WebUIRuntime {
|
|
|
118
125
|
this.app.route("/api/db", createDBRoutes());
|
|
119
126
|
this.app.route("/api/plugin-config", createPluginConfigRoutes());
|
|
120
127
|
this.app.route("/api/service-config", createServiceConfigRoutes());
|
|
128
|
+
this.app.route("/api/adapter-config", createAdapterConfigRoutes());
|
|
121
129
|
this.app.route("/api/meme", createMemeRoutes());
|
|
122
130
|
this.app.route("/api/data-management", createDataManagementRoutes());
|
|
123
131
|
this.app.route("/api/stranger", createStrangerRoutes());
|
|
@@ -250,9 +258,13 @@ const webUIService: MiokuService = {
|
|
|
250
258
|
ensureAuthConfig();
|
|
251
259
|
await ensureWebUIDist();
|
|
252
260
|
await runtime.initRoutes();
|
|
261
|
+
// 启动密钥定时巡检:即便长时间没有前端访问 / 登录请求,
|
|
262
|
+
// 也会在密钥接近过期时主动刷新并通知主人。
|
|
263
|
+
startAuthRefreshTimer();
|
|
253
264
|
},
|
|
254
265
|
|
|
255
266
|
async dispose() {
|
|
267
|
+
stopAuthRefreshTimer();
|
|
256
268
|
runtime.stop();
|
|
257
269
|
},
|
|
258
270
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-service-webui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "Mioku WebUI 服务",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -22,15 +22,14 @@
|
|
|
22
22
|
"adm-zip": "^0.5.16",
|
|
23
23
|
"fflate": "^0.8.2",
|
|
24
24
|
"gray-matter": "^4.0.3",
|
|
25
|
-
"hono": "4.12.14"
|
|
25
|
+
"hono": "4.12.14",
|
|
26
|
+
"systeminformation": "^5.31.6"
|
|
26
27
|
},
|
|
27
28
|
"peerDependencies": {
|
|
28
|
-
"mioku-service-ai": "^0.
|
|
29
|
-
"mioki": "^0.16.0"
|
|
29
|
+
"mioku-service-ai": "^4.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/bun": "^1.3.14",
|
|
33
|
-
"mioki": "^0.16.0",
|
|
34
33
|
"mioku": "workspace:*",
|
|
35
34
|
"mioku-service-ai": "workspace:*"
|
|
36
35
|
}
|
package/port-utils.ts
CHANGED
package/routes/access-control.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { logger } from "
|
|
4
|
+
import { logger } from "mioku";
|
|
5
5
|
import { readJsonFile, writeJsonFile, NODE_MODULES_DIR } from "../utils";
|
|
6
6
|
|
|
7
7
|
const ACCESS_CONFIG_PATH = path.resolve(
|
|
8
8
|
process.cwd(),
|
|
9
|
-
"config/
|
|
9
|
+
"config/core/access-control.json",
|
|
10
10
|
);
|
|
11
11
|
|
|
12
12
|
const DEFAULT_CONFIG = {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { loadAdapterConfigPage } from "../config-page-loader";
|
|
5
|
+
import { getAdapterConfigs, updateAdapterConfig } from "../system";
|
|
6
|
+
|
|
7
|
+
function listAdaptersFromNodeModules(): string[] {
|
|
8
|
+
const modulesPath = path.join(process.cwd(), "node_modules");
|
|
9
|
+
if (!fs.existsSync(modulesPath)) return [];
|
|
10
|
+
const adapters: string[] = [];
|
|
11
|
+
const entries = fs.readdirSync(modulesPath, { withFileTypes: true });
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (!entry.name.startsWith("mioku-adapter-")) continue;
|
|
14
|
+
const fullPath = path.join(modulesPath, entry.name);
|
|
15
|
+
let stat: fs.Stats;
|
|
16
|
+
try {
|
|
17
|
+
stat = fs.lstatSync(fullPath);
|
|
18
|
+
} catch {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (!stat.isDirectory() && !stat.isSymbolicLink()) continue;
|
|
22
|
+
adapters.push(entry.name.replace(/^mioku-adapter-/, ""));
|
|
23
|
+
}
|
|
24
|
+
return adapters.sort((a, b) => a.localeCompare(b));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createAdapterConfigRoutes() {
|
|
28
|
+
const app = new Hono();
|
|
29
|
+
|
|
30
|
+
app.get("/overview", (c) => {
|
|
31
|
+
const adapters = listAdaptersFromNodeModules();
|
|
32
|
+
const overview = adapters
|
|
33
|
+
.map((name) => {
|
|
34
|
+
const manifest = loadAdapterConfigPage(name);
|
|
35
|
+
return {
|
|
36
|
+
name,
|
|
37
|
+
title: manifest?.title || name,
|
|
38
|
+
description: manifest?.description,
|
|
39
|
+
hasPage: manifest?.hasCustomPage || false,
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
.filter((a) => a.hasPage);
|
|
43
|
+
|
|
44
|
+
return c.json({ ok: true, data: overview });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
app.get("/:name/page", (c) => {
|
|
48
|
+
const name = c.req.param("name");
|
|
49
|
+
const manifest = loadAdapterConfigPage(name);
|
|
50
|
+
if (!manifest) {
|
|
51
|
+
return c.json({ ok: true, data: null });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return c.json({
|
|
55
|
+
ok: true,
|
|
56
|
+
data: {
|
|
57
|
+
plugin: manifest.plugin,
|
|
58
|
+
title: manifest.title,
|
|
59
|
+
description: manifest.description,
|
|
60
|
+
markdown: manifest.markdown,
|
|
61
|
+
fields: manifest.fields,
|
|
62
|
+
hasCustomPage: manifest.hasCustomPage,
|
|
63
|
+
configs: {
|
|
64
|
+
[name]: getAdapterConfigs(name),
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
app.get("/:name", (c) => {
|
|
71
|
+
const name = c.req.param("name");
|
|
72
|
+
return c.json({ ok: true, data: getAdapterConfigs(name) });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
app.put("/:name", async (c) => {
|
|
76
|
+
const name = c.req.param("name");
|
|
77
|
+
const body = await c.req.json();
|
|
78
|
+
const data = updateAdapterConfig(name, body);
|
|
79
|
+
return c.json({ ok: true, data });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return app;
|
|
83
|
+
}
|
package/routes/ai.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { logger } from "
|
|
2
|
+
import { logger } from "mioku";
|
|
3
3
|
import { getChatConfig, updateChatConfig } from "../system";
|
|
4
4
|
import { listStickerCatalog } from "../database";
|
|
5
5
|
import aiService from "mioku-service-ai";
|
|
@@ -332,18 +332,42 @@ export function createAIRoutes() {
|
|
|
332
332
|
modelId: body?.modelId,
|
|
333
333
|
name: body?.name,
|
|
334
334
|
capabilities: body?.capabilities,
|
|
335
|
+
thinkingLevel: body?.thinkingLevel,
|
|
335
336
|
});
|
|
336
337
|
return c.json({ ok: true, data: model });
|
|
337
338
|
});
|
|
338
339
|
|
|
340
|
+
app.put("/models/:id", async (c) => {
|
|
341
|
+
const api = getAIApi();
|
|
342
|
+
if (!api?.setModelThinkingLevel) {
|
|
343
|
+
return c.json({ ok: false, error: "AI_SERVICE_UNAVAILABLE" }, 503);
|
|
344
|
+
}
|
|
345
|
+
const id = decodeURIComponent(c.req.param("id"));
|
|
346
|
+
const body = await c.req.json();
|
|
347
|
+
const rawLevel = body?.thinkingLevel;
|
|
348
|
+
const level =
|
|
349
|
+
rawLevel === null || rawLevel === undefined || rawLevel === ""
|
|
350
|
+
? undefined
|
|
351
|
+
: String(rawLevel);
|
|
352
|
+
logger.info(`[webui-action] ai.model.thinking.update`, {
|
|
353
|
+
id,
|
|
354
|
+
level: level ?? "(default)",
|
|
355
|
+
});
|
|
356
|
+
const ok = api.setModelThinkingLevel(id, level as any);
|
|
357
|
+
return c.json({ ok });
|
|
358
|
+
});
|
|
359
|
+
|
|
339
360
|
app.delete("/models/:id", (c) => {
|
|
340
361
|
const api = getAIApi();
|
|
341
|
-
if (!api?.removeCustomModel) {
|
|
362
|
+
if (!api?.removeModel && !api?.removeCustomModel) {
|
|
342
363
|
return c.json({ ok: false, error: "AI_SERVICE_UNAVAILABLE" }, 503);
|
|
343
364
|
}
|
|
344
365
|
const id = decodeURIComponent(c.req.param("id"));
|
|
345
366
|
logger.info(`[webui-action] ai.model.remove`, { id });
|
|
346
|
-
|
|
367
|
+
const ok = api.removeModel
|
|
368
|
+
? Boolean(api.removeModel(id))
|
|
369
|
+
: Boolean(api.removeCustomModel(id));
|
|
370
|
+
return c.json({ ok });
|
|
347
371
|
});
|
|
348
372
|
|
|
349
373
|
app.get("/roles", (c) => {
|
|
@@ -354,6 +378,38 @@ export function createAIRoutes() {
|
|
|
354
378
|
return c.json({ ok: true, data: api.getRoleBindings() });
|
|
355
379
|
});
|
|
356
380
|
|
|
381
|
+
app.get("/fallback", (c) => {
|
|
382
|
+
const api = getAIApi();
|
|
383
|
+
const chain = api?.getMainFallbackChain?.() ?? [];
|
|
384
|
+
return c.json({
|
|
385
|
+
ok: true,
|
|
386
|
+
data: {
|
|
387
|
+
fallback: chain,
|
|
388
|
+
liveFallback: chain,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
app.put("/fallback", async (c) => {
|
|
394
|
+
const api = getAIApi();
|
|
395
|
+
if (!api?.setMainFallbackChain) {
|
|
396
|
+
return c.json({ ok: false, error: "AI_SERVICE_UNAVAILABLE" }, 503);
|
|
397
|
+
}
|
|
398
|
+
const body = await c.req.json();
|
|
399
|
+
const raw = Array.isArray(body?.fallback) ? body.fallback : [];
|
|
400
|
+
const fallback = Array.from(
|
|
401
|
+
new Set(
|
|
402
|
+
raw
|
|
403
|
+
.map((id: unknown) => String(id || "").trim())
|
|
404
|
+
.filter((id: string) => id.includes("/") && id.length > 1),
|
|
405
|
+
),
|
|
406
|
+
);
|
|
407
|
+
logger.info(`[webui-action] ai.fallback.update`, { count: fallback.length });
|
|
408
|
+
api.setMainFallbackChain(fallback);
|
|
409
|
+
const live = api.getMainFallbackChain?.() ?? fallback;
|
|
410
|
+
return c.json({ ok: true, data: { fallback: live, liveFallback: live } });
|
|
411
|
+
});
|
|
412
|
+
|
|
357
413
|
app.put("/roles", async (c) => {
|
|
358
414
|
const api = getAIApi();
|
|
359
415
|
if (!api?.setRoleBinding) {
|
package/routes/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { logger } from "
|
|
2
|
+
import { logger } from "mioku";
|
|
3
3
|
import {
|
|
4
4
|
getMiokuConfig,
|
|
5
5
|
updateMiokuConfig,
|
|
@@ -25,7 +25,7 @@ export function createConfigRoutes() {
|
|
|
25
25
|
admins: body?.admins?.length,
|
|
26
26
|
napcat: body?.napcat?.length,
|
|
27
27
|
plugins: body?.plugins?.length,
|
|
28
|
-
|
|
28
|
+
coreConfigured: Boolean(body?.core),
|
|
29
29
|
});
|
|
30
30
|
const data = updateMiokuConfig(body);
|
|
31
31
|
return c.json({ ok: true, data });
|
|
@@ -569,7 +569,7 @@ export function createDataManagementRoutes() {
|
|
|
569
569
|
|
|
570
570
|
const file = await fs.promises.readFile(filePath);
|
|
571
571
|
c.header("content-type", "application/zip");
|
|
572
|
-
c.header("content-disposition", `attachment; filename="${path.basename(filePath)}"`);
|
|
572
|
+
c.header("content-disposition", `attachment); filename="${path.basename(filePath)}"`);
|
|
573
573
|
return c.body(file);
|
|
574
574
|
});
|
|
575
575
|
|
package/routes/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createConfigRoutes } from "./config";
|
|
|
2
2
|
export { createWebUISettingsRoutes } from "./webui";
|
|
3
3
|
export { createAIRoutes } from "./ai";
|
|
4
4
|
export { createDBRoutes, createPluginConfigRoutes, createMemeRoutes, createServiceConfigRoutes } from "./db";
|
|
5
|
+
export { createAdapterConfigRoutes } from "./adapter-config";
|
|
5
6
|
export { createManageRoutes } from "./manage";
|
|
6
7
|
export { createStoreRoutes } from "./store";
|
|
7
8
|
export { createDataManagementRoutes } from "./data-management";
|
package/routes/manage.ts
CHANGED
|
@@ -27,6 +27,9 @@ export function createManageRoutes() {
|
|
|
27
27
|
app.get("/services", (c) =>
|
|
28
28
|
c.json({ ok: true, data: listManagedPackages("service") }),
|
|
29
29
|
);
|
|
30
|
+
app.get("/adapters", (c) =>
|
|
31
|
+
c.json({ ok: true, data: listManagedPackages("adapter") }),
|
|
32
|
+
);
|
|
30
33
|
app.get("/plugins/overview", async (c) =>
|
|
31
34
|
c.json({ ok: true, data: await listManagedPackagesWithUpdates("plugin") }),
|
|
32
35
|
);
|
|
@@ -43,6 +46,14 @@ export function createManageRoutes() {
|
|
|
43
46
|
const result = await getManagedPackageDetail(name, "service");
|
|
44
47
|
return c.json(result);
|
|
45
48
|
});
|
|
49
|
+
app.get("/adapters/overview", async (c) =>
|
|
50
|
+
c.json({ ok: true, data: await listManagedPackagesWithUpdates("adapter") }),
|
|
51
|
+
);
|
|
52
|
+
app.get("/adapters/:name", async (c) => {
|
|
53
|
+
const name = c.req.param("name");
|
|
54
|
+
const result = await getManagedPackageDetail(name, "adapter");
|
|
55
|
+
return c.json(result);
|
|
56
|
+
});
|
|
46
57
|
|
|
47
58
|
app.post("/install", async (c) => {
|
|
48
59
|
const body = (await c.req.json()) as InstallRequest;
|
|
@@ -53,7 +64,7 @@ export function createManageRoutes() {
|
|
|
53
64
|
app.post("/check-update", async (c) => {
|
|
54
65
|
const body = (await c.req.json()) as {
|
|
55
66
|
name: string;
|
|
56
|
-
target: "plugin" | "service";
|
|
67
|
+
target: "plugin" | "service" | "adapter";
|
|
57
68
|
};
|
|
58
69
|
const result = await checkUpdate(body.name, body.target);
|
|
59
70
|
return c.json(result);
|
|
@@ -82,7 +93,7 @@ export function createManageRoutes() {
|
|
|
82
93
|
const body = (await c.req.json()) as Partial<ChangeRepoRequest>;
|
|
83
94
|
const result = await changeManagedPackageRepo(
|
|
84
95
|
String(body.name || ""),
|
|
85
|
-
(body.target || "plugin") as "plugin" | "service",
|
|
96
|
+
(body.target || "plugin") as "plugin" | "service" | "adapter",
|
|
86
97
|
String(body.repoUrl || ""),
|
|
87
98
|
);
|
|
88
99
|
return c.json(result);
|
package/routes/store.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface OfficialServiceEntry {
|
|
|
19
19
|
interface OfficialRegistry {
|
|
20
20
|
plugins?: Record<string, OfficialPluginEntry>;
|
|
21
21
|
services?: Record<string, OfficialServiceEntry>;
|
|
22
|
+
adapters?: Record<string, OfficialPluginEntry>;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
let cachedOfficialRegistry: OfficialRegistry | null = null;
|
|
@@ -56,9 +57,11 @@ async function readOfficialRegistry(force = false): Promise<OfficialRegistry> {
|
|
|
56
57
|
function buildOfficialMaps(registry: OfficialRegistry): {
|
|
57
58
|
plugins: Map<string, OfficialPluginEntry>;
|
|
58
59
|
services: Map<string, OfficialServiceEntry>;
|
|
60
|
+
adapters: Map<string, OfficialPluginEntry>;
|
|
59
61
|
} {
|
|
60
62
|
const plugins = new Map<string, OfficialPluginEntry>();
|
|
61
63
|
const services = new Map<string, OfficialServiceEntry>();
|
|
64
|
+
const adapters = new Map<string, OfficialPluginEntry>();
|
|
62
65
|
|
|
63
66
|
for (const [, entry] of Object.entries(registry.plugins || {})) {
|
|
64
67
|
if (entry?.npm) {
|
|
@@ -72,21 +75,25 @@ function buildOfficialMaps(registry: OfficialRegistry): {
|
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
for (const [, entry] of Object.entries(registry.adapters || {})) {
|
|
79
|
+
if (entry?.npm) {
|
|
80
|
+
adapters.set(entry.npm, entry);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
return name.startsWith("mioku-service-");
|
|
84
|
+
return { plugins, services, adapters };
|
|
80
85
|
}
|
|
81
86
|
|
|
82
|
-
function inferTypeFromPackageName(name: string): "plugin" | "service" | null {
|
|
87
|
+
function inferTypeFromPackageName(name: string): "plugin" | "service" | "adapter" | null {
|
|
83
88
|
if (name.startsWith("mioku-plugin-")) return "plugin";
|
|
84
89
|
if (name.startsWith("mioku-service-")) return "service";
|
|
90
|
+
if (name.startsWith("mioku-adapter-")) return "adapter";
|
|
85
91
|
return null;
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
function inferDisplayName(packageName: string, type: "plugin" | "service"): string {
|
|
89
|
-
const prefix =
|
|
94
|
+
function inferDisplayName(packageName: string, type: "plugin" | "service" | "adapter"): string {
|
|
95
|
+
const prefix =
|
|
96
|
+
type === "plugin" ? "mioku-plugin-" : type === "service" ? "mioku-service-" : "mioku-adapter-";
|
|
90
97
|
return packageName.startsWith(prefix)
|
|
91
98
|
? packageName.slice(prefix.length)
|
|
92
99
|
: packageName;
|
|
@@ -189,7 +196,9 @@ export function createStoreRoutes() {
|
|
|
189
196
|
const officialEntry =
|
|
190
197
|
type === "plugin"
|
|
191
198
|
? officialMaps.plugins.get(packageName)
|
|
192
|
-
:
|
|
199
|
+
: type === "service"
|
|
200
|
+
? officialMaps.services.get(packageName)
|
|
201
|
+
: officialMaps.adapters.get(packageName);
|
|
193
202
|
const repositoryUrl = normalizeRepositoryUrl(latest?.repository);
|
|
194
203
|
|
|
195
204
|
return c.json({
|
|
@@ -213,11 +222,13 @@ export function createStoreRoutes() {
|
|
|
213
222
|
requiredServices: Array.isArray(latest?.mioku?.services)
|
|
214
223
|
? latest.mioku.services
|
|
215
224
|
: [],
|
|
216
|
-
installTarget:
|
|
225
|
+
installTarget: type,
|
|
217
226
|
installPath:
|
|
218
227
|
type === "plugin"
|
|
219
228
|
? `plugins/${inferDisplayName(packageName, "plugin")}`
|
|
220
|
-
:
|
|
229
|
+
: type === "service"
|
|
230
|
+
? getServiceInstallPath(latest, packageName)
|
|
231
|
+
: `adapters/${inferDisplayName(packageName, "adapter")}`,
|
|
221
232
|
},
|
|
222
233
|
});
|
|
223
234
|
} catch (error: any) {
|
package/routes/stranger.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import {
|
|
2
|
+
import type { Bot } from "mioku";
|
|
3
|
+
import {connectedBots,
|
|
4
|
+
logger} from "mioku";
|
|
3
5
|
|
|
4
6
|
interface StrangerInfoResponse {
|
|
5
7
|
ok: boolean;
|
|
@@ -18,7 +20,7 @@ const FETCH_TIMEOUT_MS = 4000;
|
|
|
18
20
|
const nicknameCache = new Map<string, CacheEntry>();
|
|
19
21
|
const inflight = new Map<string, Promise<string | null>>();
|
|
20
22
|
|
|
21
|
-
function getConnectedBots():
|
|
23
|
+
function getConnectedBots(): Bot[] {
|
|
22
24
|
return Array.from(connectedBots.values()).filter(Boolean);
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -48,23 +50,20 @@ function setCached(userId: string, nickname: string | null): void {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
async function fetchNicknameFromBot(
|
|
51
|
-
bot:
|
|
53
|
+
bot: Bot,
|
|
52
54
|
userId: string,
|
|
53
55
|
): Promise<string | null> {
|
|
54
|
-
const controller = new AbortController();
|
|
55
|
-
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
56
56
|
try {
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const nickname = String(data?.nickname ?? "").trim();
|
|
57
|
+
const info = await bot.getFriendInfo(userId);
|
|
58
|
+
const nickname = String(info?.nickname ?? "").trim();
|
|
60
59
|
return nickname || null;
|
|
61
|
-
} catch (error
|
|
60
|
+
} catch (error) {
|
|
62
61
|
logger.warn(
|
|
63
|
-
`stranger-info: bot ${bot
|
|
62
|
+
`stranger-info: bot ${bot.bot_id} 查询 ${userId} 失败: ${
|
|
63
|
+
error instanceof Error ? error.message : String(error)
|
|
64
|
+
}`,
|
|
64
65
|
);
|
|
65
66
|
return null;
|
|
66
|
-
} finally {
|
|
67
|
-
clearTimeout(timer);
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -77,9 +76,7 @@ async function lookupNickname(userId: string): Promise<string | null> {
|
|
|
77
76
|
|
|
78
77
|
const promise = (async () => {
|
|
79
78
|
const bots = getConnectedBots();
|
|
80
|
-
if (bots.length === 0)
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
79
|
+
if (bots.length === 0) return null;
|
|
83
80
|
const results = await Promise.all(
|
|
84
81
|
bots.map((bot) => fetchNicknameFromBot(bot, userId)),
|
|
85
82
|
);
|
|
@@ -105,7 +102,7 @@ export function createStrangerRoutes() {
|
|
|
105
102
|
const app = new Hono();
|
|
106
103
|
|
|
107
104
|
app.post("/info", async (c) => {
|
|
108
|
-
const body = await c.req.json().catch(() => ({}));
|
|
105
|
+
const body = (await c.req.json().catch(() => ({}))) as { user_id?: unknown };
|
|
109
106
|
const userId = normalizeUserId(body?.user_id);
|
|
110
107
|
if (!isValidQQNumber(userId)) {
|
|
111
108
|
return c.json(
|
|
@@ -129,8 +126,10 @@ export function createStrangerRoutes() {
|
|
|
129
126
|
ok: true,
|
|
130
127
|
data: { userId, nickname },
|
|
131
128
|
} satisfies StrangerInfoResponse);
|
|
132
|
-
} catch (error
|
|
133
|
-
logger.error(
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logger.error(
|
|
131
|
+
`stranger-info 失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
132
|
+
);
|
|
134
133
|
return c.json(
|
|
135
134
|
{
|
|
136
135
|
ok: false,
|
package/system.ts
CHANGED
|
@@ -3,7 +3,9 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import AdmZip from "adm-zip";
|
|
6
|
-
import
|
|
6
|
+
import systemInfo from "systeminformation";
|
|
7
|
+
import {connectedBots,
|
|
8
|
+
logger} from "mioku";
|
|
7
9
|
import type {
|
|
8
10
|
InstallRequest,
|
|
9
11
|
ManagedTarget,
|
|
@@ -87,81 +89,16 @@ interface MiokiRuntimeConfig {
|
|
|
87
89
|
[key: string]: any;
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
const SYSTEM_PLUGIN_NAMES = new Set(["
|
|
92
|
+
const SYSTEM_PLUGIN_NAMES = new Set(["chat", "help"]);
|
|
91
93
|
const SYSTEM_SERVICE_NAMES = new Set(["ai", "config", "help", "screenshot"]);
|
|
92
|
-
const
|
|
94
|
+
const CORE_CONFIG_PATH = path.join(
|
|
93
95
|
process.cwd(),
|
|
94
96
|
"config",
|
|
95
|
-
"
|
|
97
|
+
"core",
|
|
96
98
|
"base.json",
|
|
97
99
|
);
|
|
98
100
|
|
|
99
|
-
export interface
|
|
100
|
-
update: {
|
|
101
|
-
enabled: boolean;
|
|
102
|
-
selectTimeoutMs: number;
|
|
103
|
-
};
|
|
104
|
-
install: {
|
|
105
|
-
enabled: boolean;
|
|
106
|
-
};
|
|
107
|
-
market: {
|
|
108
|
-
enabled: boolean;
|
|
109
|
-
};
|
|
110
|
-
restart: {
|
|
111
|
-
enabled: boolean;
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const DEFAULT_BOOT_SYSTEM_COMMANDS_CONFIG: BootSystemCommandsConfig = {
|
|
116
|
-
update: {
|
|
117
|
-
enabled: true,
|
|
118
|
-
selectTimeoutMs: 60000,
|
|
119
|
-
},
|
|
120
|
-
install: {
|
|
121
|
-
enabled: true,
|
|
122
|
-
},
|
|
123
|
-
market: {
|
|
124
|
-
enabled: true,
|
|
125
|
-
},
|
|
126
|
-
restart: {
|
|
127
|
-
enabled: true,
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
function normalizeBootSystemCommandsConfig(
|
|
132
|
-
input: any,
|
|
133
|
-
): BootSystemCommandsConfig {
|
|
134
|
-
const merged = deepMerge(
|
|
135
|
-
cloneJson(DEFAULT_BOOT_SYSTEM_COMMANDS_CONFIG),
|
|
136
|
-
input || {},
|
|
137
|
-
);
|
|
138
|
-
const rawTimeout = Number(merged?.update?.selectTimeoutMs);
|
|
139
|
-
return {
|
|
140
|
-
update: {
|
|
141
|
-
enabled: Boolean(merged?.update?.enabled),
|
|
142
|
-
selectTimeoutMs:
|
|
143
|
-
Number.isFinite(rawTimeout) && rawTimeout >= 1000
|
|
144
|
-
? Math.floor(rawTimeout)
|
|
145
|
-
: DEFAULT_BOOT_SYSTEM_COMMANDS_CONFIG.update.selectTimeoutMs,
|
|
146
|
-
},
|
|
147
|
-
install: {
|
|
148
|
-
enabled: Boolean(merged?.install?.enabled),
|
|
149
|
-
},
|
|
150
|
-
market: {
|
|
151
|
-
enabled: Boolean(merged?.market?.enabled),
|
|
152
|
-
},
|
|
153
|
-
restart: {
|
|
154
|
-
enabled: Boolean(merged?.restart?.enabled),
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
interface AccessRuleConfig {
|
|
160
|
-
whitelist: Array<string | number>;
|
|
161
|
-
blacklist: Array<string | number>;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface BootSystemConfig {
|
|
101
|
+
export interface CoreSystemConfig {
|
|
165
102
|
likeCommand: {
|
|
166
103
|
enabled: boolean;
|
|
167
104
|
keyword: string;
|
|
@@ -173,47 +110,31 @@ export interface BootSystemConfig {
|
|
|
173
110
|
};
|
|
174
111
|
group: {
|
|
175
112
|
minMemberCount: number;
|
|
176
|
-
welcome: {
|
|
177
|
-
enabled: boolean;
|
|
178
|
-
mode: "ai" | "text";
|
|
179
|
-
text: string;
|
|
180
|
-
aiPrompt: string;
|
|
181
|
-
};
|
|
182
113
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
114
|
+
autoUpdate: {
|
|
115
|
+
enabled: boolean;
|
|
116
|
+
time: string;
|
|
117
|
+
frequency: "daily" | "weekly" | "monthly";
|
|
186
118
|
};
|
|
187
119
|
}
|
|
188
120
|
|
|
189
|
-
const
|
|
121
|
+
const DEFAULT_CORE_SYSTEM_CONFIG: CoreSystemConfig = {
|
|
190
122
|
likeCommand: {
|
|
191
123
|
enabled: true,
|
|
192
124
|
keyword: "赞我",
|
|
193
125
|
likeTimes: 10,
|
|
194
|
-
reactionEmojiId:
|
|
126
|
+
reactionEmojiId: 201,
|
|
195
127
|
},
|
|
196
128
|
friend: {
|
|
197
|
-
autoApprove:
|
|
129
|
+
autoApprove: true,
|
|
198
130
|
},
|
|
199
131
|
group: {
|
|
200
132
|
minMemberCount: 0,
|
|
201
|
-
welcome: {
|
|
202
|
-
enabled: true,
|
|
203
|
-
mode: "ai",
|
|
204
|
-
text: "欢迎 {user} 加入 {group}",
|
|
205
|
-
aiPrompt: "",
|
|
206
|
-
},
|
|
207
133
|
},
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
},
|
|
213
|
-
group: {
|
|
214
|
-
whitelist: [],
|
|
215
|
-
blacklist: [],
|
|
216
|
-
},
|
|
134
|
+
autoUpdate: {
|
|
135
|
+
enabled: true,
|
|
136
|
+
time: "03:00",
|
|
137
|
+
frequency: "daily",
|
|
217
138
|
},
|
|
218
139
|
};
|
|
219
140
|
|
|
@@ -238,7 +159,9 @@ function isSystemServiceName(name: string): boolean {
|
|
|
238
159
|
}
|
|
239
160
|
|
|
240
161
|
function getTargetRoot(target: ManagedTarget): string {
|
|
241
|
-
|
|
162
|
+
if (target === "plugin") return PLUGINS_DIR;
|
|
163
|
+
if (target === "service") return SERVICES_DIR;
|
|
164
|
+
return NODE_MODULES_DIR;
|
|
242
165
|
}
|
|
243
166
|
|
|
244
167
|
async function getCurrentBranchName(dir: string): Promise<string> {
|
|
@@ -310,17 +233,11 @@ function cloneJson<T>(value: T): T {
|
|
|
310
233
|
return JSON.parse(JSON.stringify(value)) as T;
|
|
311
234
|
}
|
|
312
235
|
|
|
313
|
-
function
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function normalizeBootSystemConfig(input: any): BootSystemConfig {
|
|
321
|
-
const merged = deepMerge(cloneJson(DEFAULT_BOOT_SYSTEM_CONFIG), input || {});
|
|
322
|
-
const legacyPrivateFilter = input?.messageFilter?.private;
|
|
323
|
-
const userFilterSource = input?.messageFilter?.user ?? legacyPrivateFilter;
|
|
236
|
+
function normalizeCoreSystemConfig(input: any): CoreSystemConfig {
|
|
237
|
+
const merged = deepMerge(cloneJson(DEFAULT_CORE_SYSTEM_CONFIG), input || {});
|
|
238
|
+
const validFreq = ["daily", "weekly", "monthly"].includes(
|
|
239
|
+
merged?.autoUpdate?.frequency,
|
|
240
|
+
);
|
|
324
241
|
|
|
325
242
|
return {
|
|
326
243
|
likeCommand: {
|
|
@@ -329,17 +246,17 @@ function normalizeBootSystemConfig(input: any): BootSystemConfig {
|
|
|
329
246
|
typeof merged?.likeCommand?.keyword === "string" &&
|
|
330
247
|
merged.likeCommand.keyword.trim()
|
|
331
248
|
? merged.likeCommand.keyword.trim()
|
|
332
|
-
:
|
|
249
|
+
: DEFAULT_CORE_SYSTEM_CONFIG.likeCommand.keyword,
|
|
333
250
|
likeTimes:
|
|
334
251
|
typeof merged?.likeCommand?.likeTimes === "number" &&
|
|
335
252
|
Number.isFinite(merged.likeCommand.likeTimes)
|
|
336
253
|
? merged.likeCommand.likeTimes
|
|
337
|
-
:
|
|
254
|
+
: DEFAULT_CORE_SYSTEM_CONFIG.likeCommand.likeTimes,
|
|
338
255
|
reactionEmojiId:
|
|
339
256
|
typeof merged?.likeCommand?.reactionEmojiId === "number" &&
|
|
340
257
|
Number.isFinite(merged.likeCommand.reactionEmojiId)
|
|
341
258
|
? merged.likeCommand.reactionEmojiId
|
|
342
|
-
:
|
|
259
|
+
: DEFAULT_CORE_SYSTEM_CONFIG.likeCommand.reactionEmojiId,
|
|
343
260
|
},
|
|
344
261
|
friend: {
|
|
345
262
|
autoApprove: Boolean(merged?.friend?.autoApprove),
|
|
@@ -349,23 +266,18 @@ function normalizeBootSystemConfig(input: any): BootSystemConfig {
|
|
|
349
266
|
typeof merged?.group?.minMemberCount === "number" &&
|
|
350
267
|
Number.isFinite(merged.group.minMemberCount)
|
|
351
268
|
? merged.group.minMemberCount
|
|
352
|
-
:
|
|
353
|
-
welcome: {
|
|
354
|
-
enabled: Boolean(merged?.group?.welcome?.enabled),
|
|
355
|
-
mode: merged?.group?.welcome?.mode === "text" ? "text" : "ai",
|
|
356
|
-
text:
|
|
357
|
-
typeof merged?.group?.welcome?.text === "string"
|
|
358
|
-
? merged.group.welcome.text
|
|
359
|
-
: DEFAULT_BOOT_SYSTEM_CONFIG.group.welcome.text,
|
|
360
|
-
aiPrompt:
|
|
361
|
-
typeof merged?.group?.welcome?.aiPrompt === "string"
|
|
362
|
-
? merged.group.welcome.aiPrompt
|
|
363
|
-
: DEFAULT_BOOT_SYSTEM_CONFIG.group.welcome.aiPrompt,
|
|
364
|
-
},
|
|
269
|
+
: DEFAULT_CORE_SYSTEM_CONFIG.group.minMemberCount,
|
|
365
270
|
},
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
271
|
+
autoUpdate: {
|
|
272
|
+
enabled: Boolean(merged?.autoUpdate?.enabled),
|
|
273
|
+
time:
|
|
274
|
+
typeof merged?.autoUpdate?.time === "string" &&
|
|
275
|
+
merged.autoUpdate.time.trim()
|
|
276
|
+
? merged.autoUpdate.time.trim()
|
|
277
|
+
: DEFAULT_CORE_SYSTEM_CONFIG.autoUpdate.time,
|
|
278
|
+
frequency: validFreq
|
|
279
|
+
? merged.autoUpdate.frequency
|
|
280
|
+
: DEFAULT_CORE_SYSTEM_CONFIG.autoUpdate.frequency,
|
|
369
281
|
},
|
|
370
282
|
};
|
|
371
283
|
}
|
|
@@ -408,8 +320,8 @@ function readRootPackageJson(): any {
|
|
|
408
320
|
|
|
409
321
|
function readMiokiPackageJson(): any | null {
|
|
410
322
|
const candidates = [
|
|
411
|
-
path.join(process.cwd(), "node_modules", "
|
|
412
|
-
path.join(process.cwd(), "..", "node_modules", "
|
|
323
|
+
path.join(process.cwd(), "node_modules", "mioku", "package.json"),
|
|
324
|
+
path.join(process.cwd(), "..", "node_modules", "mioku", "package.json"),
|
|
413
325
|
];
|
|
414
326
|
for (const candidate of candidates) {
|
|
415
327
|
if (fs.existsSync(candidate)) {
|
|
@@ -484,20 +396,20 @@ export function updateWebUISettings(
|
|
|
484
396
|
return next;
|
|
485
397
|
}
|
|
486
398
|
|
|
487
|
-
export function
|
|
488
|
-
ensureDir(path.dirname(
|
|
489
|
-
const raw = readJsonFile<any>(
|
|
490
|
-
const normalized =
|
|
491
|
-
writeJsonFile(
|
|
399
|
+
export function getCoreSystemConfig(): CoreSystemConfig {
|
|
400
|
+
ensureDir(path.dirname(CORE_CONFIG_PATH));
|
|
401
|
+
const raw = readJsonFile<any>(CORE_CONFIG_PATH, DEFAULT_CORE_SYSTEM_CONFIG);
|
|
402
|
+
const normalized = normalizeCoreSystemConfig(raw);
|
|
403
|
+
writeJsonFile(CORE_CONFIG_PATH, normalized);
|
|
492
404
|
return normalized;
|
|
493
405
|
}
|
|
494
406
|
|
|
495
|
-
export function
|
|
496
|
-
input: Partial<
|
|
497
|
-
):
|
|
498
|
-
const current =
|
|
499
|
-
const next =
|
|
500
|
-
writeJsonFile(
|
|
407
|
+
export function updateCoreSystemConfig(
|
|
408
|
+
input: Partial<CoreSystemConfig>,
|
|
409
|
+
): CoreSystemConfig {
|
|
410
|
+
const current = getCoreSystemConfig();
|
|
411
|
+
const next = normalizeCoreSystemConfig(deepMerge(current, input || {}));
|
|
412
|
+
writeJsonFile(CORE_CONFIG_PATH, next);
|
|
501
413
|
return next;
|
|
502
414
|
}
|
|
503
415
|
|
|
@@ -523,17 +435,31 @@ function assertSafePackageName(name: string): string {
|
|
|
523
435
|
return trimmed;
|
|
524
436
|
}
|
|
525
437
|
|
|
438
|
+
function npmPrefixOf(target: ManagedTarget): string {
|
|
439
|
+
if (target === "plugin") return "mioku-plugin-";
|
|
440
|
+
if (target === "service") return "mioku-service-";
|
|
441
|
+
return "mioku-adapter-";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function fullPackageName(target: ManagedTarget, name: string): string {
|
|
445
|
+
const prefix = npmPrefixOf(target);
|
|
446
|
+
return name.startsWith(prefix) ? name : `${prefix}${name}`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function isNpmManagedName(name: string): boolean {
|
|
450
|
+
return (
|
|
451
|
+
name.startsWith("mioku-plugin-") ||
|
|
452
|
+
name.startsWith("mioku-service-") ||
|
|
453
|
+
name.startsWith("mioku-adapter-")
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
526
457
|
function resolveManagedDir(target: ManagedTarget, name: string): string {
|
|
527
458
|
const safeName = assertSafePackageName(name);
|
|
528
459
|
const root = path.resolve(getTargetRoot(target));
|
|
529
460
|
|
|
530
|
-
// 对于 npm 包,前端传入的是不带前缀的名称如 "
|
|
531
|
-
|
|
532
|
-
if (target === "plugin" && !safeName.startsWith("mioku-plugin-")) {
|
|
533
|
-
fullName = `mioku-plugin-${safeName}`;
|
|
534
|
-
} else if (target === "service" && !safeName.startsWith("mioku-service-")) {
|
|
535
|
-
fullName = `mioku-service-${safeName}`;
|
|
536
|
-
}
|
|
461
|
+
// 对于 npm 包,前端传入的是不带前缀的名称如 "chat",需要还原完整包名
|
|
462
|
+
const fullName = fullPackageName(target, safeName);
|
|
537
463
|
|
|
538
464
|
const dir = path.resolve(root, fullName);
|
|
539
465
|
if (!dir.startsWith(`${root}${path.sep}`)) {
|
|
@@ -608,7 +534,7 @@ function parseGitHubRepo(
|
|
|
608
534
|
|
|
609
535
|
const patterns = [
|
|
610
536
|
/^https?:\/\/github\.com\/([^/]+)\/([^/#?]+?)(?:\.git)?(?:[/?#].*)?$/i,
|
|
611
|
-
/^git@github\.com:
|
|
537
|
+
/^git@github\.com: [^/]+\/([^/#?]+?)(?:\.git)?$/i,
|
|
612
538
|
/^ssh:\/\/git@github\.com\/([^/]+)\/([^/#?]+?)(?:\.git)?$/i,
|
|
613
539
|
];
|
|
614
540
|
|
|
@@ -927,6 +853,9 @@ export function listManagedPackages(
|
|
|
927
853
|
if (target === "service") {
|
|
928
854
|
return listServicesFromNodeModules();
|
|
929
855
|
}
|
|
856
|
+
if (target === "adapter") {
|
|
857
|
+
return listAdaptersFromNodeModules();
|
|
858
|
+
}
|
|
930
859
|
const root = getTargetRoot(target);
|
|
931
860
|
ensureDir(root);
|
|
932
861
|
const names = fs
|
|
@@ -1007,6 +936,34 @@ function listServicesFromNodeModules(): Array<Record<string, any>> {
|
|
|
1007
936
|
return services.sort((a, b) => a.name.localeCompare(b.name));
|
|
1008
937
|
}
|
|
1009
938
|
|
|
939
|
+
function listAdaptersFromNodeModules(): Array<Record<string, any>> {
|
|
940
|
+
const adapters: Array<Record<string, any>> = [];
|
|
941
|
+
const modulesPath = NODE_MODULES_DIR;
|
|
942
|
+
if (!fs.existsSync(modulesPath)) return adapters;
|
|
943
|
+
|
|
944
|
+
const entries = fs.readdirSync(modulesPath, { withFileTypes: true });
|
|
945
|
+
for (const entry of entries) {
|
|
946
|
+
if (!entry.name.startsWith("mioku-adapter-")) continue;
|
|
947
|
+
const fullPath = path.join(modulesPath, entry.name);
|
|
948
|
+
const stat = fs.lstatSync(fullPath);
|
|
949
|
+
if (!stat.isDirectory() && !stat.isSymbolicLink()) continue;
|
|
950
|
+
const name = entry.name.replace(/^mioku-adapter-/, "");
|
|
951
|
+
const pkg = readPackageJson(fullPath);
|
|
952
|
+
adapters.push({
|
|
953
|
+
name,
|
|
954
|
+
path: fullPath,
|
|
955
|
+
version: pkg?.version ?? "0.0.0",
|
|
956
|
+
description: pkg?.description ?? "",
|
|
957
|
+
hasGit: fs.existsSync(path.join(fullPath, ".git")),
|
|
958
|
+
isSystemPlugin: false,
|
|
959
|
+
isSystemService: false,
|
|
960
|
+
repository: getRepositoryFromPackage(pkg),
|
|
961
|
+
requiredServices: pkg?.mioku?.services ?? [],
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return adapters.sort((a, b) => a.name.localeCompare(b.name));
|
|
965
|
+
}
|
|
966
|
+
|
|
1010
967
|
export async function installManagedPackage(
|
|
1011
968
|
input: InstallRequest,
|
|
1012
969
|
): Promise<Record<string, any>> {
|
|
@@ -1046,8 +1003,7 @@ export async function installManagedPackage(
|
|
|
1046
1003
|
const installedName =
|
|
1047
1004
|
afterDeps.find(
|
|
1048
1005
|
(name) =>
|
|
1049
|
-
!beforeDeps.has(name) &&
|
|
1050
|
-
(name.startsWith("mioku-plugin-") || name.startsWith("mioku-service-")),
|
|
1006
|
+
!beforeDeps.has(name) && isNpmManagedName(name),
|
|
1051
1007
|
) || pkgName;
|
|
1052
1008
|
|
|
1053
1009
|
const packageJsonPath = path.join(
|
|
@@ -1082,8 +1038,7 @@ export async function checkUpdate(
|
|
|
1082
1038
|
name: string,
|
|
1083
1039
|
target: ManagedTarget,
|
|
1084
1040
|
): Promise<Record<string, any>> {
|
|
1085
|
-
const isNpmPackage =
|
|
1086
|
-
name.startsWith("mioku-plugin-") || name.startsWith("mioku-service-");
|
|
1041
|
+
const isNpmPackage = isNpmManagedName(name);
|
|
1087
1042
|
|
|
1088
1043
|
if (isNpmPackage) {
|
|
1089
1044
|
// 使用 npm view 检查 npm 包更新
|
|
@@ -1138,9 +1093,7 @@ export async function updateManagedPackage(
|
|
|
1138
1093
|
const installCmd = getInstallCommand();
|
|
1139
1094
|
|
|
1140
1095
|
// 检查是 npm 包还是 git 包
|
|
1141
|
-
const isNpmPackage =
|
|
1142
|
-
input.name.startsWith("mioku-plugin-") ||
|
|
1143
|
-
input.name.startsWith("mioku-service-");
|
|
1096
|
+
const isNpmPackage = isNpmManagedName(input.name);
|
|
1144
1097
|
const npmPackagePath = path.join(
|
|
1145
1098
|
process.cwd(),
|
|
1146
1099
|
"node_modules",
|
|
@@ -1214,11 +1167,9 @@ export async function removeManagedPackage(
|
|
|
1214
1167
|
}
|
|
1215
1168
|
|
|
1216
1169
|
// construct full package name from short name
|
|
1217
|
-
const fullName =
|
|
1170
|
+
const fullName = isNpmManagedName(input.name)
|
|
1218
1171
|
? input.name
|
|
1219
|
-
: input.target
|
|
1220
|
-
? `mioku-plugin-${input.name}`
|
|
1221
|
-
: `mioku-service-${input.name}`;
|
|
1172
|
+
: fullPackageName(input.target, input.name);
|
|
1222
1173
|
|
|
1223
1174
|
const installCmd = getInstallCommand();
|
|
1224
1175
|
const removeArgs = installCmd.args.map((a) =>
|
|
@@ -1316,9 +1267,9 @@ export async function getManagedPackageDetail(
|
|
|
1316
1267
|
): Promise<Record<string, any>> {
|
|
1317
1268
|
// 插件名称前端传入时没有前缀,服务有前缀
|
|
1318
1269
|
const isNpmPackage =
|
|
1319
|
-
name
|
|
1320
|
-
|
|
1321
|
-
(target === "
|
|
1270
|
+
isNpmManagedName(name) ||
|
|
1271
|
+
(target === "plugin" && /^[a-zA-Z0-9_-]+$/.test(name)) ||
|
|
1272
|
+
(target === "adapter" && /^[a-zA-Z0-9_-]+$/.test(name));
|
|
1322
1273
|
|
|
1323
1274
|
// NPM 包从 npm registry 获取信息
|
|
1324
1275
|
if (isNpmPackage || target === "service") {
|
|
@@ -1327,6 +1278,8 @@ export async function getManagedPackageDetail(
|
|
|
1327
1278
|
fullName = `mioku-plugin-${name}`;
|
|
1328
1279
|
} else if (target === "service" && !name.startsWith("mioku-service-")) {
|
|
1329
1280
|
fullName = `mioku-service-${name}`;
|
|
1281
|
+
} else if (target === "adapter" && !name.startsWith("mioku-adapter-")) {
|
|
1282
|
+
fullName = `mioku-adapter-${name}`;
|
|
1330
1283
|
}
|
|
1331
1284
|
|
|
1332
1285
|
// 从 npm registry 获取包信息
|
|
@@ -1790,12 +1743,11 @@ async function notifyOwnerWebUIDownloaded(version: string): Promise<void> {
|
|
|
1790
1743
|
return;
|
|
1791
1744
|
}
|
|
1792
1745
|
|
|
1793
|
-
const bots = Array.from(connectedBots.values());
|
|
1746
|
+
const bots = Array.from(connectedBots.values()).filter(Boolean);
|
|
1794
1747
|
if (bots.length === 0) {
|
|
1795
1748
|
return;
|
|
1796
1749
|
}
|
|
1797
1750
|
|
|
1798
|
-
const bot = bots[0];
|
|
1799
1751
|
const settings = getWebUISettings();
|
|
1800
1752
|
const auth = readJsonFile<{ token?: string }>(AUTH_PATH, {});
|
|
1801
1753
|
const url = `http://${settings.host}:${settings.port}`;
|
|
@@ -1807,13 +1759,26 @@ async function notifyOwnerWebUIDownloaded(version: string): Promise<void> {
|
|
|
1807
1759
|
].join("\n");
|
|
1808
1760
|
|
|
1809
1761
|
for (const ownerId of owners) {
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1762
|
+
let notified = false;
|
|
1763
|
+
let lastError: unknown;
|
|
1764
|
+
for (const bot of bots) {
|
|
1765
|
+
if (!bot || (typeof bot.online === "boolean" && !bot.online)) {
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
try {
|
|
1769
|
+
await bot.sendMessage({ type: "private", user_id: ownerId }, message);
|
|
1770
|
+
logger.info(
|
|
1771
|
+
`[webui] 已通过 ${bot.adapter ?? "bot"}/${bot.bot_id ?? "?"} 通知主人 ${ownerId} WebUI 已就绪`,
|
|
1772
|
+
);
|
|
1773
|
+
notified = true;
|
|
1774
|
+
break;
|
|
1775
|
+
} catch (err) {
|
|
1776
|
+
lastError = err;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
if (!notified) {
|
|
1780
|
+
const msg = formatSendError(lastError);
|
|
1781
|
+
logger.warn(`[webui] 通知主人 ${ownerId} 失败: ${msg}`);
|
|
1817
1782
|
}
|
|
1818
1783
|
}
|
|
1819
1784
|
}
|
|
@@ -1844,7 +1809,6 @@ export async function notifyOwnersAuthTokenRefreshed(
|
|
|
1844
1809
|
return;
|
|
1845
1810
|
}
|
|
1846
1811
|
|
|
1847
|
-
const bot = bots[0];
|
|
1848
1812
|
const settings = getWebUISettings();
|
|
1849
1813
|
const url = `http://${settings.host}:${settings.port}`;
|
|
1850
1814
|
const expireText = new Date(expiresAt).toLocaleString("zh-CN");
|
|
@@ -1856,19 +1820,49 @@ export async function notifyOwnersAuthTokenRefreshed(
|
|
|
1856
1820
|
].join("\n");
|
|
1857
1821
|
|
|
1858
1822
|
for (const ownerId of owners) {
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1823
|
+
let notified = false;
|
|
1824
|
+
let lastError: unknown;
|
|
1825
|
+
for (const bot of bots) {
|
|
1826
|
+
if (!bot || (typeof bot.online === "boolean" && !bot.online)) {
|
|
1827
|
+
continue;
|
|
1828
|
+
}
|
|
1829
|
+
try {
|
|
1830
|
+
await bot.sendMessage(
|
|
1831
|
+
{ type: "private", user_id: ownerId },
|
|
1832
|
+
message,
|
|
1833
|
+
);
|
|
1834
|
+
logger.info(
|
|
1835
|
+
`[webui] 已通过 ${bot.adapter ?? "bot"}/${bot.bot_id ?? "?"} 通知主人 ${ownerId} 密钥更新`,
|
|
1836
|
+
);
|
|
1837
|
+
notified = true;
|
|
1838
|
+
break;
|
|
1839
|
+
} catch (err) {
|
|
1840
|
+
lastError = err;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (!notified) {
|
|
1844
|
+
const msg = formatSendError(lastError);
|
|
1865
1845
|
logger.warn(
|
|
1866
|
-
`[webui] 通知主人 ${ownerId} 密钥更新失败: ${
|
|
1846
|
+
`[webui] 通知主人 ${ownerId} 密钥更新失败: ${msg}`,
|
|
1867
1847
|
);
|
|
1868
1848
|
}
|
|
1869
1849
|
}
|
|
1870
1850
|
}
|
|
1871
1851
|
|
|
1852
|
+
/** 格式化 sendMessage 抛出的错误,方便排查(icqq 的 ApiRejection 带 code/message)。 */
|
|
1853
|
+
function formatSendError(err: unknown): string {
|
|
1854
|
+
if (!err) return "未知错误";
|
|
1855
|
+
if (typeof err === "string") return err;
|
|
1856
|
+
if (err instanceof Error) {
|
|
1857
|
+
const code = (err as { code?: number }).code;
|
|
1858
|
+
if (typeof code === "number" && Number.isFinite(code)) {
|
|
1859
|
+
return `${err.message} (code=${code})`;
|
|
1860
|
+
}
|
|
1861
|
+
return err.message;
|
|
1862
|
+
}
|
|
1863
|
+
return String(err);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1872
1866
|
export async function updateWebUIDistFromRelease(): Promise<
|
|
1873
1867
|
Record<string, any>
|
|
1874
1868
|
> {
|
|
@@ -2113,7 +2107,7 @@ async function getCpuUsagePercent(): Promise<number> {
|
|
|
2113
2107
|
return Number((((totalDelta - idleDelta) / totalDelta) * 100).toFixed(1));
|
|
2114
2108
|
}
|
|
2115
2109
|
|
|
2116
|
-
function toHttpBaseUrl(bot:
|
|
2110
|
+
function toHttpBaseUrl(bot: { options?: { protocol?: string; host?: string; port?: number } }): string {
|
|
2117
2111
|
const protocol = String(bot.options?.protocol || "ws");
|
|
2118
2112
|
const httpProtocol = protocol === "wss" ? "https" : "http";
|
|
2119
2113
|
const host = bot.options?.host || "127.0.0.1";
|
|
@@ -2148,15 +2142,23 @@ function normalizeVersionSpec(input: string): string {
|
|
|
2148
2142
|
return matched?.[0] || cleaned;
|
|
2149
2143
|
}
|
|
2150
2144
|
|
|
2151
|
-
async function getBotDetails(bot:
|
|
2145
|
+
async function getBotDetails(bot: import("mioku").Bot): Promise<Record<string, any>> {
|
|
2146
|
+
const botAny = bot as unknown as {
|
|
2147
|
+
uin?: number | string;
|
|
2148
|
+
user_id?: number | string;
|
|
2149
|
+
name?: string;
|
|
2150
|
+
options?: { protocol?: string; host?: string; port?: number };
|
|
2151
|
+
app_version?: string;
|
|
2152
|
+
};
|
|
2153
|
+
const qq = String(botAny.uin ?? botAny.user_id ?? bot.bot_id);
|
|
2152
2154
|
const base = {
|
|
2153
|
-
botId: bot
|
|
2154
|
-
qq
|
|
2155
|
-
nickname: bot
|
|
2156
|
-
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${
|
|
2157
|
-
online:
|
|
2158
|
-
napcatVersion:
|
|
2159
|
-
napcatApiBase: toHttpBaseUrl(
|
|
2155
|
+
botId: bot.bot_id,
|
|
2156
|
+
qq,
|
|
2157
|
+
nickname: bot.nickname || botAny.name || "Unknown Bot",
|
|
2158
|
+
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${qq}&s=160`,
|
|
2159
|
+
online: bot.online,
|
|
2160
|
+
napcatVersion: botAny.app_version || "unknown",
|
|
2161
|
+
napcatApiBase: toHttpBaseUrl(botAny),
|
|
2160
2162
|
groupCount: 0,
|
|
2161
2163
|
friendCount: 0,
|
|
2162
2164
|
onlineDurationMs: 0,
|
|
@@ -2165,8 +2167,8 @@ async function getBotDetails(bot: any): Promise<Record<string, any>> {
|
|
|
2165
2167
|
|
|
2166
2168
|
try {
|
|
2167
2169
|
const [status, versionInfo, groups, friends] = await Promise.all([
|
|
2168
|
-
bot.
|
|
2169
|
-
bot.
|
|
2170
|
+
bot.sendApi<{ stat?: { start_time?: number; online?: boolean }; online?: boolean }>("get_status").catch(() => null),
|
|
2171
|
+
bot.sendApi<{ app_version?: string }>("get_version_info").catch(() => null),
|
|
2170
2172
|
bot.getGroupList().catch(() => []),
|
|
2171
2173
|
bot.getFriendList().catch(() => []),
|
|
2172
2174
|
]);
|
|
@@ -2307,6 +2309,24 @@ export function updateChatConfig(fileName: string, data: any): any {
|
|
|
2307
2309
|
return data;
|
|
2308
2310
|
}
|
|
2309
2311
|
|
|
2312
|
+
export function getAdapterConfigs(adapterName: string): any {
|
|
2313
|
+
const rootPkg = readRootPackageJson();
|
|
2314
|
+
const adapters = rootPkg?.mioku?.adapters ?? {};
|
|
2315
|
+
const safeName = String(adapterName || "").trim().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2316
|
+
return adapters[safeName] ?? {};
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
export function updateAdapterConfig(adapterName: string, data: any): any {
|
|
2320
|
+
const safeName = String(adapterName || "").trim().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2321
|
+
if (!safeName) throw new Error("适配器名称无效");
|
|
2322
|
+
const rootPkg = readRootPackageJson();
|
|
2323
|
+
rootPkg.mioku = rootPkg.mioku ?? {};
|
|
2324
|
+
rootPkg.mioku.adapters = rootPkg.mioku.adapters ?? {};
|
|
2325
|
+
rootPkg.mioku.adapters[safeName] = data ?? {};
|
|
2326
|
+
writeRootPackageJson(rootPkg);
|
|
2327
|
+
return data;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2310
2330
|
export interface MiokuConfig {
|
|
2311
2331
|
owners: number[];
|
|
2312
2332
|
admins: number[];
|
|
@@ -2318,7 +2338,7 @@ export interface MiokuConfig {
|
|
|
2318
2338
|
token: string;
|
|
2319
2339
|
}>;
|
|
2320
2340
|
plugins: string[];
|
|
2321
|
-
|
|
2341
|
+
core: CoreSystemConfig;
|
|
2322
2342
|
}
|
|
2323
2343
|
|
|
2324
2344
|
export function getMiokuConfig(): MiokuConfig {
|
|
@@ -2333,7 +2353,7 @@ export function getMiokuConfig(): MiokuConfig {
|
|
|
2333
2353
|
admins: Array.isArray(miokiConfig.admins) ? miokiConfig.admins : [],
|
|
2334
2354
|
napcat: Array.isArray(miokiConfig.napcat) ? miokiConfig.napcat : [],
|
|
2335
2355
|
plugins: Array.isArray(miokiConfig.plugins) ? miokiConfig.plugins : [],
|
|
2336
|
-
|
|
2356
|
+
core: getCoreSystemConfig(),
|
|
2337
2357
|
};
|
|
2338
2358
|
}
|
|
2339
2359
|
|
|
@@ -2344,13 +2364,16 @@ export function updateMiokuConfig(config: Partial<MiokuConfig>): MiokuConfig {
|
|
|
2344
2364
|
admins: Array.isArray(config.admins) ? config.admins : current.admins,
|
|
2345
2365
|
napcat: Array.isArray(config.napcat) ? config.napcat : current.napcat,
|
|
2346
2366
|
plugins: Array.isArray(config.plugins) ? config.plugins : current.plugins,
|
|
2347
|
-
|
|
2367
|
+
core: config.core ? updateCoreSystemConfig(config.core) : current.core,
|
|
2348
2368
|
};
|
|
2349
2369
|
|
|
2350
2370
|
const localConfig = readJsonFile<any>(LOCAL_CONFIG_PATH, { mioki: {} });
|
|
2351
2371
|
localConfig.mioki = {
|
|
2352
2372
|
...localConfig.mioki,
|
|
2353
|
-
|
|
2373
|
+
owners: updated.owners,
|
|
2374
|
+
admins: updated.admins,
|
|
2375
|
+
napcat: updated.napcat,
|
|
2376
|
+
plugins: updated.plugins,
|
|
2354
2377
|
};
|
|
2355
2378
|
writeJsonFile(LOCAL_CONFIG_PATH, localConfig);
|
|
2356
2379
|
|
|
@@ -2421,9 +2444,6 @@ export function togglePlugin(name: string, enabled: boolean): PluginStatusItem {
|
|
|
2421
2444
|
if (enabled) {
|
|
2422
2445
|
plugins.add(safeName);
|
|
2423
2446
|
} else {
|
|
2424
|
-
if (safeName.toLowerCase() === "boot") {
|
|
2425
|
-
throw new Error("boot 插件不可关闭");
|
|
2426
|
-
}
|
|
2427
2447
|
plugins.delete(safeName);
|
|
2428
2448
|
}
|
|
2429
2449
|
|
package/types.ts
CHANGED
package/utils.ts
CHANGED
|
@@ -133,9 +133,14 @@ export function isNpmPackageName(name: string): boolean {
|
|
|
133
133
|
|
|
134
134
|
export function resolveNpmPackageName(
|
|
135
135
|
name: string,
|
|
136
|
-
target: "plugin" | "service",
|
|
136
|
+
target: "plugin" | "service" | "adapter",
|
|
137
137
|
): string {
|
|
138
|
-
const prefix =
|
|
138
|
+
const prefix =
|
|
139
|
+
target === "plugin"
|
|
140
|
+
? "mioku-plugin-"
|
|
141
|
+
: target === "service"
|
|
142
|
+
? "mioku-service-"
|
|
143
|
+
: "mioku-adapter-";
|
|
139
144
|
if (name.startsWith(prefix)) {
|
|
140
145
|
return name;
|
|
141
146
|
}
|