mioku-service-webui 4.8.1 → 4.9.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/package.json +1 -1
- package/system.ts +255 -142
package/package.json
CHANGED
package/system.ts
CHANGED
|
@@ -4,8 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import AdmZip from "adm-zip";
|
|
6
6
|
import systemInfo from "systeminformation";
|
|
7
|
-
import {connectedBots,
|
|
8
|
-
|
|
7
|
+
import { buildAdapterReport, connectedBots, logger } from "mioku";
|
|
8
|
+
import type { AdapterInstanceStatus, Bot } from "mioku";
|
|
9
9
|
import type {
|
|
10
10
|
InstallRequest,
|
|
11
11
|
ManagedTarget,
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
import {
|
|
18
18
|
AUTH_PATH,
|
|
19
19
|
CHAT_CONFIG_DIR,
|
|
20
|
+
CONFIG_DIR,
|
|
20
21
|
defaultWebUISettings,
|
|
21
22
|
ensureDir,
|
|
22
23
|
getInstallCommand,
|
|
@@ -641,6 +642,11 @@ interface ManagedPackageUpdateInfo {
|
|
|
641
642
|
error?: string;
|
|
642
643
|
}
|
|
643
644
|
|
|
645
|
+
interface NpmPackageUpdateInfo extends ManagedPackageUpdateInfo {
|
|
646
|
+
currentVersion: string;
|
|
647
|
+
latestVersion: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
644
650
|
interface ManagedPackageUpdateCacheEntry {
|
|
645
651
|
checkedAt: number;
|
|
646
652
|
info: ManagedPackageUpdateInfo;
|
|
@@ -728,6 +734,65 @@ function isManagedUpdateCacheFresh(
|
|
|
728
734
|
return Date.now() - entry.checkedAt < MANAGED_UPDATE_CACHE_TTL_MS;
|
|
729
735
|
}
|
|
730
736
|
|
|
737
|
+
/** 本地已安装版本:node_modules/<fullName>/package.json */
|
|
738
|
+
function readLocalPackageVersion(fullName: string): string {
|
|
739
|
+
const pkg = readPackageJson(path.join(NODE_MODULES_DIR, fullName));
|
|
740
|
+
return pkg?.version || "";
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
async function getNpmLatestVersion(fullName: string): Promise<string> {
|
|
744
|
+
try {
|
|
745
|
+
const data = await fetchJson(
|
|
746
|
+
`https://registry.npmjs.org/${encodeURIComponent(fullName)}`,
|
|
747
|
+
);
|
|
748
|
+
return String(data?.["dist-tags"]?.latest || "").trim();
|
|
749
|
+
} catch {
|
|
750
|
+
return "";
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function npmManagedUpdateError(
|
|
755
|
+
fullName: string,
|
|
756
|
+
error: string,
|
|
757
|
+
): NpmPackageUpdateInfo {
|
|
758
|
+
return {
|
|
759
|
+
state: "unknown",
|
|
760
|
+
hasUpdates: false,
|
|
761
|
+
behind: 0,
|
|
762
|
+
changelog: [],
|
|
763
|
+
error,
|
|
764
|
+
currentVersion: readLocalPackageVersion(fullName),
|
|
765
|
+
latestVersion: "",
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async function checkNpmManagedUpdate(
|
|
770
|
+
fullName: string,
|
|
771
|
+
): Promise<NpmPackageUpdateInfo> {
|
|
772
|
+
const current = readLocalPackageVersion(fullName);
|
|
773
|
+
let latest = "";
|
|
774
|
+
try {
|
|
775
|
+
latest = await getNpmLatestVersion(fullName);
|
|
776
|
+
} catch (error: any) {
|
|
777
|
+
return npmManagedUpdateError(
|
|
778
|
+
fullName,
|
|
779
|
+
error?.message || "UPDATE_CHECK_FAILED",
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
if (!latest) {
|
|
783
|
+
return npmManagedUpdateError(fullName, "无法获取 npm 最新版本");
|
|
784
|
+
}
|
|
785
|
+
const hasUpdates = Boolean(current) && current !== latest;
|
|
786
|
+
return {
|
|
787
|
+
state: hasUpdates ? "has-updates" : "up-to-date",
|
|
788
|
+
hasUpdates,
|
|
789
|
+
behind: hasUpdates ? 1 : 0,
|
|
790
|
+
changelog: [],
|
|
791
|
+
currentVersion: current || "0.0.0",
|
|
792
|
+
latestVersion: latest,
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
731
796
|
async function refreshManagedUpdatesInBackground(
|
|
732
797
|
target: ManagedTarget,
|
|
733
798
|
packages: Array<Record<string, any>>,
|
|
@@ -736,21 +801,9 @@ async function refreshManagedUpdatesInBackground(
|
|
|
736
801
|
const name = String(item.name || "");
|
|
737
802
|
if (!name) continue;
|
|
738
803
|
|
|
739
|
-
|
|
740
|
-
setCachedManagedUpdateInfo(target, name, {
|
|
741
|
-
state: "no-git",
|
|
742
|
-
hasUpdates: false,
|
|
743
|
-
behind: 0,
|
|
744
|
-
changelog: [],
|
|
745
|
-
error: "NOT_GIT_REPO",
|
|
746
|
-
});
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
|
|
804
|
+
const fullName = fullPackageName(target, name);
|
|
750
805
|
try {
|
|
751
|
-
const updateInfo = await
|
|
752
|
-
String(item.path || ""),
|
|
753
|
-
);
|
|
806
|
+
const updateInfo = await checkNpmManagedUpdate(fullName);
|
|
754
807
|
setCachedManagedUpdateInfo(target, name, updateInfo);
|
|
755
808
|
} catch (error: any) {
|
|
756
809
|
setCachedManagedUpdateInfo(target, name, {
|
|
@@ -770,8 +823,8 @@ function scheduleManagedUpdatesRefresh(
|
|
|
770
823
|
): void {
|
|
771
824
|
if (managedOverviewRefreshInFlight.has(target)) return;
|
|
772
825
|
|
|
826
|
+
// npm 包与 git 包统一走 registry 更新检查(已不再使用 git 仓库形式)
|
|
773
827
|
const shouldRefresh = packages.some((item) => {
|
|
774
|
-
if (!item.hasGit) return false;
|
|
775
828
|
const cached = getCachedManagedUpdateInfo(target, String(item.name || ""));
|
|
776
829
|
return !isManagedUpdateCacheFresh(cached);
|
|
777
830
|
});
|
|
@@ -1002,10 +1055,8 @@ export async function installManagedPackage(
|
|
|
1002
1055
|
...Object.keys(afterPkg?.devDependencies || {}),
|
|
1003
1056
|
];
|
|
1004
1057
|
const installedName =
|
|
1005
|
-
afterDeps.find(
|
|
1006
|
-
|
|
1007
|
-
!beforeDeps.has(name) && isNpmManagedName(name),
|
|
1008
|
-
) || pkgName;
|
|
1058
|
+
afterDeps.find((name) => !beforeDeps.has(name) && isNpmManagedName(name)) ||
|
|
1059
|
+
pkgName;
|
|
1009
1060
|
|
|
1010
1061
|
const packageJsonPath = path.join(
|
|
1011
1062
|
NODE_MODULES_DIR,
|
|
@@ -1039,34 +1090,25 @@ export async function checkUpdate(
|
|
|
1039
1090
|
name: string,
|
|
1040
1091
|
target: ManagedTarget,
|
|
1041
1092
|
): Promise<Record<string, any>> {
|
|
1042
|
-
|
|
1093
|
+
// 现在插件/服务/适配器全部以 npm 包形式安装,统一走 npm 更新检查
|
|
1094
|
+
const fullName = isNpmManagedName(name)
|
|
1095
|
+
? name
|
|
1096
|
+
: fullPackageName(target, name);
|
|
1097
|
+
const localPath = path.join(NODE_MODULES_DIR, fullName);
|
|
1098
|
+
const isNpmPackage = fs.existsSync(localPath) || isNpmManagedName(name);
|
|
1043
1099
|
|
|
1044
1100
|
if (isNpmPackage) {
|
|
1045
|
-
|
|
1046
|
-
const currentVersion = await runCommand(
|
|
1047
|
-
"bun",
|
|
1048
|
-
["npm", "info", name, "version"],
|
|
1049
|
-
process.cwd(),
|
|
1050
|
-
);
|
|
1051
|
-
const latestVersion = await runCommand(
|
|
1052
|
-
"bun",
|
|
1053
|
-
["npm", "info", name, "dist-tags.latest"],
|
|
1054
|
-
process.cwd(),
|
|
1055
|
-
);
|
|
1056
|
-
const current = currentVersion.stdout?.trim() || "";
|
|
1057
|
-
const latest = latestVersion.stdout?.trim() || "";
|
|
1058
|
-
const hasUpdates = current !== latest && latest !== "";
|
|
1059
|
-
|
|
1101
|
+
const info = await checkNpmManagedUpdate(fullName);
|
|
1060
1102
|
return {
|
|
1061
1103
|
ok: true,
|
|
1062
|
-
state:
|
|
1063
|
-
hasUpdates,
|
|
1064
|
-
behind:
|
|
1065
|
-
changelog:
|
|
1104
|
+
state: info.state,
|
|
1105
|
+
hasUpdates: info.hasUpdates,
|
|
1106
|
+
behind: info.behind,
|
|
1107
|
+
changelog: info.changelog,
|
|
1066
1108
|
hasGit: false,
|
|
1067
|
-
error: null,
|
|
1068
|
-
currentVersion:
|
|
1069
|
-
latestVersion:
|
|
1109
|
+
error: info.error || null,
|
|
1110
|
+
currentVersion: info.currentVersion,
|
|
1111
|
+
latestVersion: info.latestVersion,
|
|
1070
1112
|
};
|
|
1071
1113
|
}
|
|
1072
1114
|
|
|
@@ -1093,28 +1135,30 @@ export async function updateManagedPackage(
|
|
|
1093
1135
|
): Promise<Record<string, any>> {
|
|
1094
1136
|
const installCmd = getInstallCommand();
|
|
1095
1137
|
|
|
1096
|
-
//
|
|
1097
|
-
const
|
|
1138
|
+
// 前端传短名(如 chat),统一还原为完整 npm 包名
|
|
1139
|
+
const fullName = isNpmManagedName(input.name)
|
|
1140
|
+
? input.name
|
|
1141
|
+
: fullPackageName(input.target, input.name);
|
|
1098
1142
|
const npmPackagePath = path.join(
|
|
1099
1143
|
process.cwd(),
|
|
1100
1144
|
"node_modules",
|
|
1101
|
-
|
|
1145
|
+
fullName,
|
|
1102
1146
|
"package.json",
|
|
1103
1147
|
);
|
|
1104
1148
|
|
|
1105
|
-
if (
|
|
1106
|
-
// Bun
|
|
1107
|
-
const updateArgs = installCmd.args.map((arg) =>
|
|
1108
|
-
arg === "install" ? "update" : arg,
|
|
1109
|
-
);
|
|
1149
|
+
if (fs.existsSync(npmPackagePath)) {
|
|
1150
|
+
// Bun 包更新:bun add 会拉取 npm 最新版本
|
|
1110
1151
|
const update = await runCommand(
|
|
1111
1152
|
installCmd.cmd,
|
|
1112
|
-
[...
|
|
1153
|
+
[...installCmd.args, fullName],
|
|
1113
1154
|
process.cwd(),
|
|
1114
1155
|
);
|
|
1115
1156
|
if (update.code !== 0) {
|
|
1116
|
-
throw new Error(`bun
|
|
1157
|
+
throw new Error(`bun add 更新失败: ${update.stderr || update.stdout}`);
|
|
1117
1158
|
}
|
|
1159
|
+
managedPackageUpdateCache.delete(
|
|
1160
|
+
makeManagedUpdateCacheKey(input.target, fullName),
|
|
1161
|
+
);
|
|
1118
1162
|
return {
|
|
1119
1163
|
ok: true,
|
|
1120
1164
|
restartRequired: true,
|
|
@@ -1173,9 +1217,7 @@ export async function removeManagedPackage(
|
|
|
1173
1217
|
: fullPackageName(input.target, input.name);
|
|
1174
1218
|
|
|
1175
1219
|
const installCmd = getInstallCommand();
|
|
1176
|
-
const removeArgs = installCmd.args.map((a) =>
|
|
1177
|
-
a === "add" ? "remove" : a,
|
|
1178
|
-
);
|
|
1220
|
+
const removeArgs = installCmd.args.map((a) => (a === "add" ? "remove" : a));
|
|
1179
1221
|
|
|
1180
1222
|
const result = await runCommand(
|
|
1181
1223
|
installCmd.cmd,
|
|
@@ -1194,10 +1236,15 @@ export async function removeManagedPackage(
|
|
|
1194
1236
|
try {
|
|
1195
1237
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
1196
1238
|
const mioki = pkg.mioki ?? {};
|
|
1197
|
-
const plugins: string[] = Array.isArray(mioki.plugins)
|
|
1239
|
+
const plugins: string[] = Array.isArray(mioki.plugins)
|
|
1240
|
+
? mioki.plugins
|
|
1241
|
+
: [];
|
|
1198
1242
|
const shortName = fullName.replace(/^mioku-plugin-/, "");
|
|
1199
1243
|
if (plugins.includes(shortName)) {
|
|
1200
|
-
pkg.mioki = {
|
|
1244
|
+
pkg.mioki = {
|
|
1245
|
+
...mioki,
|
|
1246
|
+
plugins: plugins.filter((p: string) => p !== shortName),
|
|
1247
|
+
};
|
|
1201
1248
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
1202
1249
|
}
|
|
1203
1250
|
} catch {
|
|
@@ -1226,17 +1273,6 @@ export async function listManagedPackagesWithUpdates(
|
|
|
1226
1273
|
return packages.map((item) => {
|
|
1227
1274
|
const name = String(item.name || "");
|
|
1228
1275
|
|
|
1229
|
-
if (!item.hasGit) {
|
|
1230
|
-
return {
|
|
1231
|
-
...item,
|
|
1232
|
-
updateState: "no-git",
|
|
1233
|
-
hasUpdates: false,
|
|
1234
|
-
behind: 0,
|
|
1235
|
-
updateError: "NOT_GIT_REPO",
|
|
1236
|
-
updateChecking: false,
|
|
1237
|
-
};
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
1276
|
const cached = getCachedManagedUpdateInfo(target, name);
|
|
1241
1277
|
if (cached) {
|
|
1242
1278
|
return {
|
|
@@ -1300,6 +1336,9 @@ export async function getManagedPackageDetail(
|
|
|
1300
1336
|
} else {
|
|
1301
1337
|
repositoryUrl = normalizeRepositoryUrl(latest?.repository);
|
|
1302
1338
|
}
|
|
1339
|
+
// npm 包以包内 repository 字段为准,缺失时回退到 mioku-lab 仓库
|
|
1340
|
+
const packageRepository = normalizeRepositoryUrl(latest?.repository);
|
|
1341
|
+
const finalRepository = packageRepository || repositoryUrl;
|
|
1303
1342
|
|
|
1304
1343
|
// 获取本地已安装版本
|
|
1305
1344
|
const localPkg =
|
|
@@ -1325,7 +1364,7 @@ export async function getManagedPackageDetail(
|
|
|
1325
1364
|
hasGit: false,
|
|
1326
1365
|
isSystemPlugin,
|
|
1327
1366
|
isSystemService,
|
|
1328
|
-
repository:
|
|
1367
|
+
repository: finalRepository,
|
|
1329
1368
|
originUrl: "",
|
|
1330
1369
|
homepage: String(latest?.homepage || "").trim(),
|
|
1331
1370
|
requiredServices: Array.isArray(latest?.mioku?.services)
|
|
@@ -1335,7 +1374,7 @@ export async function getManagedPackageDetail(
|
|
|
1335
1374
|
help: latest?.mioku?.help || null,
|
|
1336
1375
|
readme: String(data?.readme || "").trim(),
|
|
1337
1376
|
readmeFile: "README.md",
|
|
1338
|
-
updateState: "
|
|
1377
|
+
updateState: hasUpdates ? "has-updates" : "up-to-date",
|
|
1339
1378
|
hasUpdates,
|
|
1340
1379
|
behind: hasUpdates ? 1 : 0,
|
|
1341
1380
|
changelog: [],
|
|
@@ -1445,19 +1484,24 @@ export async function updateAllManagedPackages(input: {
|
|
|
1445
1484
|
const results: Array<Record<string, any>> = [];
|
|
1446
1485
|
|
|
1447
1486
|
for (const item of packages) {
|
|
1448
|
-
const
|
|
1449
|
-
if (
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1487
|
+
const name = String(item.name || "");
|
|
1488
|
+
if (!name) continue;
|
|
1489
|
+
|
|
1490
|
+
// 统一走 npm 更新检查(已不再使用 git 仓库形式)
|
|
1491
|
+
const cached = getCachedManagedUpdateInfo(input.target, name);
|
|
1492
|
+
let updateInfo: ManagedPackageUpdateInfo;
|
|
1493
|
+
if (cached && isManagedUpdateCacheFresh(cached)) {
|
|
1494
|
+
updateInfo = cached.info;
|
|
1495
|
+
} else {
|
|
1496
|
+
updateInfo = await checkNpmManagedUpdate(
|
|
1497
|
+
fullPackageName(input.target, name),
|
|
1498
|
+
);
|
|
1499
|
+
setCachedManagedUpdateInfo(input.target, name, updateInfo);
|
|
1457
1500
|
}
|
|
1501
|
+
|
|
1458
1502
|
if (!updateInfo.hasUpdates) {
|
|
1459
1503
|
results.push({
|
|
1460
|
-
name
|
|
1504
|
+
name,
|
|
1461
1505
|
ok: true,
|
|
1462
1506
|
skipped: true,
|
|
1463
1507
|
reason: updateInfo.state === "unknown" ? "CHECK_FAILED" : "UP_TO_DATE",
|
|
@@ -1468,19 +1512,19 @@ export async function updateAllManagedPackages(input: {
|
|
|
1468
1512
|
|
|
1469
1513
|
try {
|
|
1470
1514
|
const updated = await updateManagedPackage({
|
|
1471
|
-
name
|
|
1515
|
+
name,
|
|
1472
1516
|
target: input.target,
|
|
1473
1517
|
packageManager: input.packageManager,
|
|
1474
1518
|
});
|
|
1475
1519
|
results.push({
|
|
1476
|
-
name
|
|
1520
|
+
name,
|
|
1477
1521
|
ok: true,
|
|
1478
1522
|
skipped: false,
|
|
1479
1523
|
...updated,
|
|
1480
1524
|
});
|
|
1481
1525
|
} catch (error: any) {
|
|
1482
1526
|
results.push({
|
|
1483
|
-
name
|
|
1527
|
+
name,
|
|
1484
1528
|
ok: false,
|
|
1485
1529
|
skipped: false,
|
|
1486
1530
|
error: error?.message || "UPDATE_FAILED",
|
|
@@ -1828,10 +1872,7 @@ export async function notifyOwnersAuthTokenRefreshed(
|
|
|
1828
1872
|
continue;
|
|
1829
1873
|
}
|
|
1830
1874
|
try {
|
|
1831
|
-
await bot.sendMessage(
|
|
1832
|
-
{ type: "private", user_id: ownerId },
|
|
1833
|
-
message,
|
|
1834
|
-
);
|
|
1875
|
+
await bot.sendMessage({ type: "private", user_id: ownerId }, message);
|
|
1835
1876
|
logger.info(
|
|
1836
1877
|
`[webui] 已通过 ${bot.adapter ?? "bot"}/${bot.bot_id ?? "?"} 通知主人 ${ownerId} 密钥更新`,
|
|
1837
1878
|
);
|
|
@@ -1843,9 +1884,7 @@ export async function notifyOwnersAuthTokenRefreshed(
|
|
|
1843
1884
|
}
|
|
1844
1885
|
if (!notified) {
|
|
1845
1886
|
const msg = formatSendError(lastError);
|
|
1846
|
-
logger.warn(
|
|
1847
|
-
`[webui] 通知主人 ${ownerId} 密钥更新失败: ${msg}`,
|
|
1848
|
-
);
|
|
1887
|
+
logger.warn(`[webui] 通知主人 ${ownerId} 密钥更新失败: ${msg}`);
|
|
1849
1888
|
}
|
|
1850
1889
|
}
|
|
1851
1890
|
}
|
|
@@ -2108,14 +2147,6 @@ async function getCpuUsagePercent(): Promise<number> {
|
|
|
2108
2147
|
return Number((((totalDelta - idleDelta) / totalDelta) * 100).toFixed(1));
|
|
2109
2148
|
}
|
|
2110
2149
|
|
|
2111
|
-
function toHttpBaseUrl(bot: { options?: { protocol?: string; host?: string; port?: number } }): string {
|
|
2112
|
-
const protocol = String(bot.options?.protocol || "ws");
|
|
2113
|
-
const httpProtocol = protocol === "wss" ? "https" : "http";
|
|
2114
|
-
const host = bot.options?.host || "127.0.0.1";
|
|
2115
|
-
const port = bot.options?.port || 3001;
|
|
2116
|
-
return `${httpProtocol}://${host}:${port}`;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
2150
|
async function fetchYiyan(): Promise<{ text: string }> {
|
|
2120
2151
|
const controller = new AbortController();
|
|
2121
2152
|
const timer = setTimeout(() => controller.abort(), 4500);
|
|
@@ -2143,61 +2174,138 @@ function normalizeVersionSpec(input: string): string {
|
|
|
2143
2174
|
return matched?.[0] || cleaned;
|
|
2144
2175
|
}
|
|
2145
2176
|
|
|
2146
|
-
|
|
2177
|
+
/** 读取已安装适配器包的版本(与 help 状态图里 ctx.getAdapter().version 同源) */
|
|
2178
|
+
function readAdapterPackageVersion(name: string): string {
|
|
2179
|
+
const pkg = readPackageJson(
|
|
2180
|
+
path.join(NODE_MODULES_DIR, `mioku-adapter-${name}`),
|
|
2181
|
+
);
|
|
2182
|
+
return pkg?.version || "";
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/** 与 help 状态图一致:stdin 使用 help/status 配置的随机头像,未配置时用默认随机图接口 */
|
|
2186
|
+
function readStdinAvatar(): string {
|
|
2187
|
+
const statusConfig = readJsonFile<any>(
|
|
2188
|
+
path.join(CONFIG_DIR, "help", "status.json"),
|
|
2189
|
+
null,
|
|
2190
|
+
);
|
|
2191
|
+
const configured = String(statusConfig?.stdinAvatar || "").trim();
|
|
2192
|
+
if (configured) return configured;
|
|
2193
|
+
return "https://uapis.cn/api/v1/random/image?category=bq&type=eciyuan";
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
async function getBotDetails(bot: Bot): Promise<Record<string, any>> {
|
|
2147
2197
|
const botAny = bot as unknown as {
|
|
2148
2198
|
uin?: number | string;
|
|
2149
2199
|
user_id?: number | string;
|
|
2150
2200
|
name?: string;
|
|
2151
|
-
options?: { protocol?: string; host?: string; port?: number };
|
|
2152
|
-
app_version?: string;
|
|
2153
2201
|
};
|
|
2154
|
-
const
|
|
2202
|
+
const accountId = String(botAny.uin ?? botAny.user_id ?? bot.bot_id);
|
|
2203
|
+
const isStdin = bot.adapter === "stdin";
|
|
2155
2204
|
const base = {
|
|
2156
2205
|
botId: bot.bot_id,
|
|
2157
|
-
|
|
2206
|
+
accountId,
|
|
2207
|
+
qq: accountId,
|
|
2158
2208
|
nickname: bot.nickname || botAny.name || "Unknown Bot",
|
|
2159
|
-
avatar:
|
|
2209
|
+
avatar: isStdin
|
|
2210
|
+
? readStdinAvatar()
|
|
2211
|
+
: `https://q1.qlogo.cn/g?b=qq&nk=${accountId}&s=160`,
|
|
2160
2212
|
online: bot.online,
|
|
2161
|
-
|
|
2162
|
-
|
|
2213
|
+
adapter: String(bot.adapter),
|
|
2214
|
+
adapterVersion: readAdapterPackageVersion(String(bot.adapter)),
|
|
2215
|
+
implLabel: "",
|
|
2216
|
+
framework: isStdin ? "stdin" : String(bot.adapter),
|
|
2217
|
+
appVersion: "",
|
|
2218
|
+
protocolVersion: "",
|
|
2219
|
+
platform: "",
|
|
2220
|
+
platformVersion: "",
|
|
2163
2221
|
groupCount: 0,
|
|
2164
2222
|
friendCount: 0,
|
|
2223
|
+
send: 0,
|
|
2224
|
+
receive: 0,
|
|
2165
2225
|
onlineDurationMs: 0,
|
|
2166
|
-
statusText: "online",
|
|
2226
|
+
statusText: bot.online ? "online" : "offline",
|
|
2167
2227
|
};
|
|
2168
2228
|
|
|
2169
2229
|
try {
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2230
|
+
if (typeof bot.getAvatar === "function" && !isStdin) {
|
|
2231
|
+
const avatar = await bot.getAvatar().catch(() => null);
|
|
2232
|
+
if (avatar) base.avatar = avatar;
|
|
2233
|
+
}
|
|
2234
|
+
} catch {
|
|
2235
|
+
// keep qlogo fallback
|
|
2236
|
+
}
|
|
2237
|
+
return base;
|
|
2238
|
+
}
|
|
2176
2239
|
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2240
|
+
async function getBotOverviewList(): Promise<Record<string, any>[]> {
|
|
2241
|
+
const bots = Array.from(connectedBots.values());
|
|
2242
|
+
if (bots.length === 0) return [];
|
|
2243
|
+
const sorted = [...bots].sort(
|
|
2244
|
+
(a, b) => Number(b.adapter === "stdin") - Number(a.adapter === "stdin"),
|
|
2245
|
+
);
|
|
2183
2246
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2247
|
+
const adapters = sorted.map((bot) => ({
|
|
2248
|
+
name: String(bot.adapter),
|
|
2249
|
+
version: readAdapterPackageVersion(String(bot.adapter)),
|
|
2250
|
+
}));
|
|
2251
|
+
const report = await buildAdapterReport({ bots: sorted, adapters }).catch(
|
|
2252
|
+
() => null,
|
|
2253
|
+
);
|
|
2254
|
+
const instances = new Map<string, AdapterInstanceStatus>();
|
|
2255
|
+
const implOf = new Map<
|
|
2256
|
+
string,
|
|
2257
|
+
{ name: string; version?: string } | undefined
|
|
2258
|
+
>();
|
|
2259
|
+
for (const entry of report?.adapters ?? []) {
|
|
2260
|
+
implOf.set(entry.name, entry.impl);
|
|
2261
|
+
for (const instance of entry.instances) {
|
|
2262
|
+
instances.set(`${entry.name}:${instance.bot_id}`, instance);
|
|
2263
|
+
}
|
|
2200
2264
|
}
|
|
2265
|
+
|
|
2266
|
+
return Promise.all(
|
|
2267
|
+
sorted.map(async (bot) => {
|
|
2268
|
+
const detail = await getBotDetails(bot);
|
|
2269
|
+
const uin = String(detail.accountId);
|
|
2270
|
+
const adapterName = String(detail.adapter);
|
|
2271
|
+
const isStdin = adapterName === "stdin";
|
|
2272
|
+
const instance = instances.get(`${adapterName}:${uin}`);
|
|
2273
|
+
const lib = implOf.get(adapterName);
|
|
2274
|
+
const appVersion = instance?.version || lib?.version || "";
|
|
2275
|
+
const implLabel = isStdin
|
|
2276
|
+
? "stdin"
|
|
2277
|
+
: instance?.impl
|
|
2278
|
+
? instance.version
|
|
2279
|
+
? `${instance.impl}/${instance.version}`
|
|
2280
|
+
: String(instance.impl)
|
|
2281
|
+
: lib?.name
|
|
2282
|
+
? lib.version
|
|
2283
|
+
? `${lib.name} v${lib.version}`
|
|
2284
|
+
: lib.name
|
|
2285
|
+
: adapterName;
|
|
2286
|
+
|
|
2287
|
+
return {
|
|
2288
|
+
...detail,
|
|
2289
|
+
implLabel,
|
|
2290
|
+
framework: isStdin ? "stdin" : adapterName,
|
|
2291
|
+
appVersion: isStdin
|
|
2292
|
+
? String(readAdapterPackageVersion(adapterName))
|
|
2293
|
+
: appVersion,
|
|
2294
|
+
protocolVersion: instance?.protocol ?? "",
|
|
2295
|
+
platform: instance?.platform ?? "",
|
|
2296
|
+
platformVersion: instance?.platformVersion ?? "",
|
|
2297
|
+
online: instance?.online ?? bot.online,
|
|
2298
|
+
groupCount: instance?.stats.groups ?? 0,
|
|
2299
|
+
friendCount: instance?.stats.friends ?? 0,
|
|
2300
|
+
send: instance?.stats.sent ?? 0,
|
|
2301
|
+
receive: instance?.stats.received ?? 0,
|
|
2302
|
+
onlineDurationMs:
|
|
2303
|
+
bot.connected_at != null && bot.online
|
|
2304
|
+
? Math.max(0, Date.now() - bot.connected_at)
|
|
2305
|
+
: 0,
|
|
2306
|
+
};
|
|
2307
|
+
}),
|
|
2308
|
+
);
|
|
2201
2309
|
}
|
|
2202
2310
|
|
|
2203
2311
|
export async function getSystemOverview(): Promise<Record<string, any>> {
|
|
@@ -2221,9 +2329,10 @@ export async function getSystemOverview(): Promise<Record<string, any>> {
|
|
|
2221
2329
|
: 0;
|
|
2222
2330
|
const siSnapshot = await getSystemInformationSnapshot();
|
|
2223
2331
|
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2226
|
-
const selectedBot =
|
|
2332
|
+
const bots = await getBotOverviewList();
|
|
2333
|
+
// 默认展示第一个非 stdin 的 bot(stdin 是终端通道,不是真实账号)
|
|
2334
|
+
const selectedBot =
|
|
2335
|
+
bots.find((bot) => bot.adapter !== "stdin") || bots[0] || null;
|
|
2227
2336
|
|
|
2228
2337
|
return {
|
|
2229
2338
|
uptimeSeconds: process.uptime(),
|
|
@@ -2313,12 +2422,16 @@ export function updateChatConfig(fileName: string, data: any): any {
|
|
|
2313
2422
|
export function getAdapterConfigs(adapterName: string): any {
|
|
2314
2423
|
const rootPkg = readRootPackageJson();
|
|
2315
2424
|
const adapters = rootPkg?.mioku?.adapters ?? {};
|
|
2316
|
-
const safeName = String(adapterName || "")
|
|
2425
|
+
const safeName = String(adapterName || "")
|
|
2426
|
+
.trim()
|
|
2427
|
+
.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2317
2428
|
return adapters[safeName] ?? {};
|
|
2318
2429
|
}
|
|
2319
2430
|
|
|
2320
2431
|
export function updateAdapterConfig(adapterName: string, data: any): any {
|
|
2321
|
-
const safeName = String(adapterName || "")
|
|
2432
|
+
const safeName = String(adapterName || "")
|
|
2433
|
+
.trim()
|
|
2434
|
+
.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2322
2435
|
if (!safeName) throw new Error("适配器名称无效");
|
|
2323
2436
|
const rootPkg = readRootPackageJson();
|
|
2324
2437
|
rootPkg.mioku = rootPkg.mioku ?? {};
|