koishi-plugin-adapter-onebot-multi 1.0.1 → 1.0.2
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/lib/config-store.d.ts +14 -0
- package/lib/index.js +108 -8
- package/package.json +1 -1
package/lib/config-store.d.ts
CHANGED
|
@@ -20,17 +20,29 @@ export interface StoreConfig {
|
|
|
20
20
|
bots: ManagedBotConfig[];
|
|
21
21
|
loadBalance: Required<LoadBalanceConfig>;
|
|
22
22
|
}
|
|
23
|
+
type ExternalChangeListener = (next: StoreConfig, prev: StoreConfig) => void | Promise<void>;
|
|
23
24
|
export declare class ConfigStore {
|
|
24
25
|
private ctx;
|
|
25
26
|
private configRoot;
|
|
26
27
|
private configPath;
|
|
27
28
|
private config;
|
|
28
29
|
private saveTimer;
|
|
30
|
+
private reloadTimer;
|
|
31
|
+
private skipWatchUntil;
|
|
32
|
+
private watching;
|
|
33
|
+
private externalChangeListeners;
|
|
34
|
+
private logger;
|
|
35
|
+
private readonly onFileChanged;
|
|
29
36
|
constructor(ctx: Context);
|
|
30
37
|
private ensureDir;
|
|
31
38
|
private normalize;
|
|
32
39
|
private loadConfig;
|
|
40
|
+
private startWatching;
|
|
41
|
+
private stopWatching;
|
|
42
|
+
private scheduleReloadFromDisk;
|
|
43
|
+
private reloadFromDisk;
|
|
33
44
|
private saveConfigSync;
|
|
45
|
+
onExternalChange(listener: ExternalChangeListener): () => void;
|
|
34
46
|
private scheduleSave;
|
|
35
47
|
getAll(): StoreConfig;
|
|
36
48
|
getRuntime(): RuntimeConfig;
|
|
@@ -40,4 +52,6 @@ export declare class ConfigStore {
|
|
|
40
52
|
setLoadBalance(loadBalance: Partial<LoadBalanceConfig>): void;
|
|
41
53
|
setBots(bots: ManagedBotConfig[]): void;
|
|
42
54
|
flush(): void;
|
|
55
|
+
dispose(): void;
|
|
43
56
|
}
|
|
57
|
+
export {};
|
package/lib/index.js
CHANGED
|
@@ -1695,11 +1695,11 @@ var LoadBalancer = class {
|
|
|
1695
1695
|
selectedBot = this.selectBotByGreedy(validBots, botRemainingCapacity, botLoad);
|
|
1696
1696
|
}
|
|
1697
1697
|
if (!selectedBot) {
|
|
1698
|
-
const
|
|
1699
|
-
if (
|
|
1698
|
+
const unassignedValue2 = this.config.unassignedValue;
|
|
1699
|
+
if (unassignedValue2 !== void 0 && unassignedValue2 !== "") {
|
|
1700
1700
|
const lastAssignee2 = this.lastAssignees.get(channel.channelId);
|
|
1701
|
-
if (lastAssignee2 !==
|
|
1702
|
-
assignments.push({ channelId: channel.channelId, assignee:
|
|
1701
|
+
if (lastAssignee2 !== unassignedValue2) {
|
|
1702
|
+
assignments.push({ channelId: channel.channelId, assignee: unassignedValue2 });
|
|
1703
1703
|
this.logger.info(`群 ${channel.channelId} 所有 Bot 达到上限,设为未分配`);
|
|
1704
1704
|
}
|
|
1705
1705
|
} else {
|
|
@@ -1725,14 +1725,28 @@ var LoadBalancer = class {
|
|
|
1725
1725
|
const channels2 = this.botChannels.get(id);
|
|
1726
1726
|
return channels2 && channels2.size > 0;
|
|
1727
1727
|
});
|
|
1728
|
-
const
|
|
1728
|
+
const managedChannels = channels.filter((c) => this.shouldManageChannel(c.channelId));
|
|
1729
|
+
const priorityCount = managedChannels.filter((c) => c.isPriority).length;
|
|
1730
|
+
const configuredPriorityCount = new Set(this.config.priorityChannels || []).size;
|
|
1731
|
+
const unassignedValue = this.config.unassignedValue || "";
|
|
1732
|
+
let assignedTotal = 0;
|
|
1733
|
+
let unassignedTotal = 0;
|
|
1734
|
+
for (const [, assignee] of this.lastAssignees.entries()) {
|
|
1735
|
+
if (assignee && assignee !== unassignedValue) {
|
|
1736
|
+
assignedTotal++;
|
|
1737
|
+
} else {
|
|
1738
|
+
unassignedTotal++;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1729
1741
|
if (botsWithData.length > 0) {
|
|
1730
1742
|
const loads = botsWithData.map((id) => botLoad.get(id) || 0);
|
|
1731
1743
|
const avg = loads.reduce((a, b) => a + b, 0) / loads.length;
|
|
1732
1744
|
const variance = loads.reduce((sum, load) => sum + Math.pow(load - avg, 2), 0) / loads.length;
|
|
1733
1745
|
const stdDev = Math.sqrt(variance);
|
|
1734
1746
|
const loadInfo = botsWithData.map((id) => `${id}:${botLoad.get(id) || 0}`).join(", ");
|
|
1735
|
-
this.logger.info(
|
|
1747
|
+
this.logger.info(
|
|
1748
|
+
`负载均衡完成,本轮变更 ${assignments.length} 个群;当前已分配 ${assignedTotal} 个,未分配 ${unassignedTotal} 个;关键群(参与调度)${priorityCount} / 配置总数 ${configuredPriorityCount}`
|
|
1749
|
+
);
|
|
1736
1750
|
this.logger.info(`负载分布: [${loadInfo}],平均: ${avg.toFixed(1)},标准差: ${stdDev.toFixed(2)}`);
|
|
1737
1751
|
}
|
|
1738
1752
|
} catch (e) {
|
|
@@ -1914,10 +1928,12 @@ var DEFAULT_CONFIG = {
|
|
|
1914
1928
|
var ConfigStore = class {
|
|
1915
1929
|
constructor(ctx) {
|
|
1916
1930
|
this.ctx = ctx;
|
|
1931
|
+
this.logger = this.ctx.logger("adapter-onebot-multi");
|
|
1917
1932
|
this.configRoot = path.join(ctx.baseDir, "data", "adapter-onebot-multi");
|
|
1918
1933
|
this.configPath = path.join(this.configRoot, "config.yaml");
|
|
1919
1934
|
this.ensureDir(this.configRoot);
|
|
1920
1935
|
this.loadConfig();
|
|
1936
|
+
this.startWatching();
|
|
1921
1937
|
}
|
|
1922
1938
|
static {
|
|
1923
1939
|
__name(this, "ConfigStore");
|
|
@@ -1926,6 +1942,17 @@ var ConfigStore = class {
|
|
|
1926
1942
|
configPath;
|
|
1927
1943
|
config = structuredClone(DEFAULT_CONFIG);
|
|
1928
1944
|
saveTimer = null;
|
|
1945
|
+
reloadTimer = null;
|
|
1946
|
+
skipWatchUntil = 0;
|
|
1947
|
+
watching = false;
|
|
1948
|
+
externalChangeListeners = /* @__PURE__ */ new Set();
|
|
1949
|
+
logger;
|
|
1950
|
+
onFileChanged = /* @__PURE__ */ __name((curr, prev) => {
|
|
1951
|
+
if (!this.watching) return;
|
|
1952
|
+
if (curr.mtimeMs === prev.mtimeMs) return;
|
|
1953
|
+
if (Date.now() < this.skipWatchUntil) return;
|
|
1954
|
+
this.scheduleReloadFromDisk();
|
|
1955
|
+
}, "onFileChanged");
|
|
1929
1956
|
ensureDir(dir) {
|
|
1930
1957
|
if (!fs.existsSync(dir)) {
|
|
1931
1958
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -1981,15 +2008,57 @@ var ConfigStore = class {
|
|
|
1981
2008
|
const parsed = yaml.parse(content);
|
|
1982
2009
|
this.config = this.normalize(parsed);
|
|
1983
2010
|
} catch (error) {
|
|
1984
|
-
this.
|
|
2011
|
+
this.logger.warn("读取配置失败,使用默认配置: %s", error);
|
|
1985
2012
|
this.config = structuredClone(DEFAULT_CONFIG);
|
|
1986
2013
|
this.saveConfigSync();
|
|
1987
2014
|
}
|
|
1988
2015
|
}
|
|
2016
|
+
startWatching() {
|
|
2017
|
+
if (this.watching) return;
|
|
2018
|
+
this.watching = true;
|
|
2019
|
+
fs.watchFile(this.configPath, { interval: 1e3 }, this.onFileChanged);
|
|
2020
|
+
}
|
|
2021
|
+
stopWatching() {
|
|
2022
|
+
if (!this.watching) return;
|
|
2023
|
+
fs.unwatchFile(this.configPath, this.onFileChanged);
|
|
2024
|
+
this.watching = false;
|
|
2025
|
+
}
|
|
2026
|
+
scheduleReloadFromDisk() {
|
|
2027
|
+
if (this.reloadTimer) clearTimeout(this.reloadTimer);
|
|
2028
|
+
this.reloadTimer = setTimeout(() => {
|
|
2029
|
+
this.reloadTimer = null;
|
|
2030
|
+
this.reloadFromDisk();
|
|
2031
|
+
}, 200);
|
|
2032
|
+
}
|
|
2033
|
+
reloadFromDisk() {
|
|
2034
|
+
try {
|
|
2035
|
+
const content = fs.readFileSync(this.configPath, "utf8");
|
|
2036
|
+
const parsed = yaml.parse(content);
|
|
2037
|
+
const normalized = this.normalize(parsed);
|
|
2038
|
+
const prev = structuredClone(this.config);
|
|
2039
|
+
if (JSON.stringify(prev) === JSON.stringify(normalized)) return;
|
|
2040
|
+
this.config = normalized;
|
|
2041
|
+
this.logger.info("检测到配置文件变更,已重新加载。");
|
|
2042
|
+
for (const listener of this.externalChangeListeners) {
|
|
2043
|
+
Promise.resolve(listener(structuredClone(this.config), prev)).catch((error) => {
|
|
2044
|
+
this.logger.warn("配置变更回调执行失败: %s", error);
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
} catch (error) {
|
|
2048
|
+
this.logger.warn("重新加载配置失败,忽略本次变更: %s", error);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
1989
2051
|
saveConfigSync() {
|
|
2052
|
+
this.skipWatchUntil = Date.now() + 300;
|
|
1990
2053
|
const content = yaml.stringify(this.config, { indent: 2, lineWidth: 120 });
|
|
1991
2054
|
fs.writeFileSync(this.configPath, content, "utf8");
|
|
1992
2055
|
}
|
|
2056
|
+
onExternalChange(listener) {
|
|
2057
|
+
this.externalChangeListeners.add(listener);
|
|
2058
|
+
return () => {
|
|
2059
|
+
this.externalChangeListeners.delete(listener);
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
1993
2062
|
scheduleSave() {
|
|
1994
2063
|
if (this.saveTimer) clearTimeout(this.saveTimer);
|
|
1995
2064
|
this.saveTimer = setTimeout(() => {
|
|
@@ -2037,6 +2106,14 @@ var ConfigStore = class {
|
|
|
2037
2106
|
}
|
|
2038
2107
|
this.saveConfigSync();
|
|
2039
2108
|
}
|
|
2109
|
+
dispose() {
|
|
2110
|
+
if (this.reloadTimer) {
|
|
2111
|
+
clearTimeout(this.reloadTimer);
|
|
2112
|
+
this.reloadTimer = null;
|
|
2113
|
+
}
|
|
2114
|
+
this.stopWatching();
|
|
2115
|
+
this.flush();
|
|
2116
|
+
}
|
|
2040
2117
|
};
|
|
2041
2118
|
|
|
2042
2119
|
// src/index.ts
|
|
@@ -2066,6 +2143,28 @@ function apply(ctx, _config) {
|
|
|
2066
2143
|
}, "getGlobalConfig");
|
|
2067
2144
|
ctx._onebotMultiGlobalConfig = getGlobalConfig();
|
|
2068
2145
|
const loadBalancer = new LoadBalancer(ctx, configStore.getLoadBalance(), statusManager);
|
|
2146
|
+
const stopConfigWatcher = configStore.onExternalChange(async (next, prev) => {
|
|
2147
|
+
const runtimeChanged = JSON.stringify(prev.runtime) !== JSON.stringify(next.runtime);
|
|
2148
|
+
const loadBalanceChanged = JSON.stringify(prev.loadBalance) !== JSON.stringify(next.loadBalance);
|
|
2149
|
+
const botsChanged = JSON.stringify(prev.bots) !== JSON.stringify(next.bots);
|
|
2150
|
+
if (runtimeChanged) {
|
|
2151
|
+
;
|
|
2152
|
+
ctx._onebotMultiGlobalConfig = getGlobalConfig();
|
|
2153
|
+
const restartTargets = configStore.getBots().filter((bot) => bot.enabled !== false).map((bot) => bot.selfId);
|
|
2154
|
+
for (const selfId of restartTargets) {
|
|
2155
|
+
await restartBot(selfId);
|
|
2156
|
+
}
|
|
2157
|
+
logger.info("检测到 runtime 配置更新,已重启启用中的 Bot。");
|
|
2158
|
+
}
|
|
2159
|
+
if (loadBalanceChanged) {
|
|
2160
|
+
loadBalancer.updateConfig(configStore.getLoadBalance());
|
|
2161
|
+
logger.info("检测到 loadBalance 配置更新,已应用。");
|
|
2162
|
+
}
|
|
2163
|
+
if (botsChanged) {
|
|
2164
|
+
await syncBots();
|
|
2165
|
+
logger.info("检测到 bots 配置更新,已同步 Bot 状态。");
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2069
2168
|
const getBotListView = /* @__PURE__ */ __name(() => {
|
|
2070
2169
|
const runtime = statusManager.getStatus();
|
|
2071
2170
|
const runtimeMap = new Map(runtime.bots.map((b) => [String(b.selfId), b]));
|
|
@@ -2344,11 +2443,12 @@ function apply(ctx, _config) {
|
|
|
2344
2443
|
logger.info(`Bot 配置 selfId 自动更新: ${configKey} -> ${runtimeSelfId}`);
|
|
2345
2444
|
});
|
|
2346
2445
|
ctx.on("dispose", async () => {
|
|
2446
|
+
stopConfigWatcher();
|
|
2347
2447
|
for (const selfId of Array.from(managedBotIds)) {
|
|
2348
2448
|
await stopBot(selfId);
|
|
2349
2449
|
}
|
|
2350
2450
|
loadBalancer.dispose();
|
|
2351
|
-
configStore.
|
|
2451
|
+
configStore.dispose();
|
|
2352
2452
|
});
|
|
2353
2453
|
}
|
|
2354
2454
|
__name(apply, "apply");
|