mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-29 21:07:49 +08:00
fix: auto reload
reconstruct: semaphore and read write lock use undefined instead of null fix: accesscontrol auto reload config
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { CCLog } from "@/lib/ccLog";
|
||||
import { AccessConfig, UserGroupConfig, saveConfig } from "./config";
|
||||
import {
|
||||
AccessConfig,
|
||||
UserGroupConfig,
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
} from "./config";
|
||||
import { ChatBoxEvent, pullEventAs } from "@/lib/event";
|
||||
import { parseBoolean } from "@/lib/common";
|
||||
|
||||
@@ -16,19 +21,19 @@ interface CLIResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
shouldSaveConfig?: boolean;
|
||||
config?: AccessConfig;
|
||||
}
|
||||
|
||||
// CLI上下文
|
||||
interface CLIContext {
|
||||
config: AccessConfig;
|
||||
configFilepath: string;
|
||||
reloadConfig: () => void;
|
||||
log: CCLog;
|
||||
chatBox: ChatBoxPeripheral;
|
||||
}
|
||||
|
||||
function getGroupNames(context: CLIContext) {
|
||||
return context.config.usersGroups.flatMap((value) => value.groupName);
|
||||
function getGroupNames(config: AccessConfig) {
|
||||
return config.usersGroups.flatMap((value) => value.groupName);
|
||||
}
|
||||
|
||||
// 基础命令处理器
|
||||
@@ -76,10 +81,6 @@ class CLICommandProcessor {
|
||||
}
|
||||
|
||||
const ret = command.execute(args, executor, this.context);
|
||||
if (ret.success) {
|
||||
this.context.reloadConfig();
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -101,7 +102,8 @@ class CLICommandProcessor {
|
||||
}
|
||||
|
||||
if (result.shouldSaveConfig === true) {
|
||||
saveConfig(this.context.config, this.context.configFilepath);
|
||||
saveConfig(result.config!, this.context.configFilepath);
|
||||
this.context.reloadConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,26 +123,30 @@ class AddCommand implements CLICommand {
|
||||
}
|
||||
|
||||
const [groupName, playerName] = args;
|
||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
||||
|
||||
if (groupName === "admin") {
|
||||
context.config.adminGroupConfig.groupUsers.push(playerName);
|
||||
config.adminGroupConfig.groupUsers.push(playerName);
|
||||
return {
|
||||
success: true,
|
||||
message: `Add player ${playerName} to admin`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
}
|
||||
|
||||
const groupNames = getGroupNames(context);
|
||||
const groupNames = getGroupNames(config);
|
||||
|
||||
if (!groupNames.includes(groupName)) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`,
|
||||
message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(
|
||||
", ",
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const groupConfig = context.config.usersGroups.find(
|
||||
const groupConfig = config.usersGroups.find(
|
||||
(value) => value.groupName === groupName,
|
||||
);
|
||||
|
||||
@@ -161,6 +167,7 @@ class AddCommand implements CLICommand {
|
||||
success: true,
|
||||
message: `Add player ${playerName} to ${groupConfig.groupName}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -188,16 +195,19 @@ class DelCommand implements CLICommand {
|
||||
};
|
||||
}
|
||||
|
||||
const groupNames = getGroupNames(context);
|
||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
||||
const groupNames = getGroupNames(config);
|
||||
|
||||
if (!groupNames.includes(groupName)) {
|
||||
return {
|
||||
success: false,
|
||||
message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`,
|
||||
message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(
|
||||
", ",
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const groupConfig = context.config.usersGroups.find(
|
||||
const groupConfig = config.usersGroups.find(
|
||||
(value) => value.groupName === groupName,
|
||||
);
|
||||
|
||||
@@ -220,6 +230,7 @@ class DelCommand implements CLICommand {
|
||||
success: true,
|
||||
message: `Delete ${groupConfig.groupName} ${playerName}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -231,9 +242,10 @@ class ListCommand implements CLICommand {
|
||||
usage = "list";
|
||||
|
||||
execute(_args: string[], _executor: string, context: CLIContext): CLIResult {
|
||||
let message = `Admins : [ ${context.config.adminGroupConfig.groupUsers.join(", ")} ]\n`;
|
||||
const config = loadConfig(context.configFilepath)!;
|
||||
let message = `Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]\n`;
|
||||
|
||||
for (const groupConfig of context.config.usersGroups) {
|
||||
for (const groupConfig of config.usersGroups) {
|
||||
const users = groupConfig.groupUsers ?? [];
|
||||
message += `${groupConfig.groupName} : [ ${users.join(", ")} ]\n`;
|
||||
}
|
||||
@@ -269,29 +281,34 @@ class SetCommand implements CLICommand {
|
||||
};
|
||||
}
|
||||
|
||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
||||
|
||||
switch (option) {
|
||||
case "warnInterval":
|
||||
context.config.watchInterval = value;
|
||||
config.watchInterval = value;
|
||||
return {
|
||||
success: true,
|
||||
message: `Set warn interval to ${context.config.watchInterval}`,
|
||||
message: `Set warn interval to ${config.watchInterval}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
|
||||
case "detectInterval":
|
||||
context.config.detectInterval = value;
|
||||
config.detectInterval = value;
|
||||
return {
|
||||
success: true,
|
||||
message: `Set detect interval to ${context.config.detectInterval}`,
|
||||
message: `Set detect interval to ${config.detectInterval}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
|
||||
case "detectRange":
|
||||
context.config.detectRange = value;
|
||||
config.detectRange = value;
|
||||
return {
|
||||
success: true,
|
||||
message: `Set detect range to ${context.config.detectRange}`,
|
||||
message: `Set detect range to ${config.detectRange}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
|
||||
default:
|
||||
@@ -310,7 +327,8 @@ class HelpCommand implements CLICommand {
|
||||
usage = "help";
|
||||
|
||||
execute(_args: string[], _executor: string, context: CLIContext): CLIResult {
|
||||
const groupNames = getGroupNames(context);
|
||||
const config = loadConfig(context.configFilepath)!;
|
||||
const groupNames = getGroupNames(config);
|
||||
const helpMessage = `
|
||||
Command Usage: @AC /<Command> [args]
|
||||
Commands:
|
||||
@@ -378,13 +396,14 @@ class EditCommand implements CLICommand {
|
||||
}
|
||||
|
||||
const [groupName, property, valueStr] = args;
|
||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
||||
|
||||
let groupConfig: UserGroupConfig | undefined;
|
||||
|
||||
if (groupName === "admin") {
|
||||
groupConfig = context.config.adminGroupConfig;
|
||||
groupConfig = config.adminGroupConfig;
|
||||
} else {
|
||||
groupConfig = context.config.usersGroups.find(
|
||||
groupConfig = config.usersGroups.find(
|
||||
(group) => group.groupName === groupName,
|
||||
);
|
||||
}
|
||||
@@ -405,6 +424,7 @@ class EditCommand implements CLICommand {
|
||||
success: true,
|
||||
message: `Set ${groupName}.isAllowed to ${groupConfig.isAllowed}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -423,6 +443,7 @@ class EditCommand implements CLICommand {
|
||||
success: true,
|
||||
message: `Set ${groupName}.isNotice to ${groupConfig.isNotice}`,
|
||||
shouldSaveConfig: true,
|
||||
config,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -450,15 +471,16 @@ class ShowConfigCommand implements CLICommand {
|
||||
|
||||
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
|
||||
const type = args[0] || "all";
|
||||
const config = loadConfig(context.configFilepath)!;
|
||||
|
||||
switch (type) {
|
||||
case "groups": {
|
||||
let groupsMessage = `Admin Group: ${context.config.adminGroupConfig.groupName}\n`;
|
||||
groupsMessage += ` Users: [${context.config.adminGroupConfig.groupUsers.join(", ")}]\n`;
|
||||
groupsMessage += ` Allowed: ${context.config.adminGroupConfig.isAllowed}\n`;
|
||||
groupsMessage += ` notice: ${context.config.adminGroupConfig.isNotice}\n\n`;
|
||||
let groupsMessage = `Admin Group: ${config.adminGroupConfig.groupName}\n`;
|
||||
groupsMessage += ` Users: [${config.adminGroupConfig.groupUsers.join(", ")}]\n`;
|
||||
groupsMessage += ` Allowed: ${config.adminGroupConfig.isAllowed}\n`;
|
||||
groupsMessage += ` notice: ${config.adminGroupConfig.isNotice}\n\n`;
|
||||
|
||||
for (const group of context.config.usersGroups) {
|
||||
for (const group of config.usersGroups) {
|
||||
groupsMessage += `Group: ${group.groupName}\n`;
|
||||
groupsMessage += ` Users: [${(group.groupUsers ?? []).join(", ")}]\n`;
|
||||
groupsMessage += ` Allowed: ${group.isAllowed}\n`;
|
||||
@@ -474,18 +496,18 @@ class ShowConfigCommand implements CLICommand {
|
||||
|
||||
case "toast": {
|
||||
let toastMessage = "Default Toast Config:\n";
|
||||
toastMessage += ` Title: ${context.config.welcomeToastConfig.title.text}\n`;
|
||||
toastMessage += ` Message: ${context.config.welcomeToastConfig.msg.text}\n`;
|
||||
toastMessage += ` Prefix: ${context.config.welcomeToastConfig.prefix ?? "none"}\n`;
|
||||
toastMessage += ` Brackets: ${context.config.welcomeToastConfig.brackets ?? "none"}\n`;
|
||||
toastMessage += ` Bracket Color: ${context.config.welcomeToastConfig.bracketColor ?? "none"}\n\n`;
|
||||
toastMessage += ` Title: ${config.welcomeToastConfig.title.text}\n`;
|
||||
toastMessage += ` Message: ${config.welcomeToastConfig.msg.text}\n`;
|
||||
toastMessage += ` Prefix: ${config.welcomeToastConfig.prefix ?? "none"}\n`;
|
||||
toastMessage += ` Brackets: ${config.welcomeToastConfig.brackets ?? "none"}\n`;
|
||||
toastMessage += ` Bracket Color: ${config.welcomeToastConfig.bracketColor ?? "none"}\n\n`;
|
||||
|
||||
toastMessage += "Warn Toast Config:\n";
|
||||
toastMessage += ` Title: ${context.config.warnToastConfig.title.text}\n`;
|
||||
toastMessage += ` Message: ${context.config.warnToastConfig.msg.text}\n`;
|
||||
toastMessage += ` Prefix: ${context.config.warnToastConfig.prefix ?? "none"}\n`;
|
||||
toastMessage += ` Brackets: ${context.config.warnToastConfig.brackets ?? "none"}\n`;
|
||||
toastMessage += ` Bracket Color: ${context.config.warnToastConfig.bracketColor ?? "none"}`;
|
||||
toastMessage += ` Title: ${config.warnToastConfig.title.text}\n`;
|
||||
toastMessage += ` Message: ${config.warnToastConfig.msg.text}\n`;
|
||||
toastMessage += ` Prefix: ${config.warnToastConfig.prefix ?? "none"}\n`;
|
||||
toastMessage += ` Brackets: ${config.warnToastConfig.brackets ?? "none"}\n`;
|
||||
toastMessage += ` Bracket Color: ${config.warnToastConfig.bracketColor ?? "none"}`;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -494,9 +516,9 @@ class ShowConfigCommand implements CLICommand {
|
||||
}
|
||||
|
||||
case "all": {
|
||||
let allMessage = `Detect Range: ${context.config.detectRange}\n`;
|
||||
allMessage += `Detect Interval: ${context.config.detectInterval}\n`;
|
||||
allMessage += `Warn Interval: ${context.config.watchInterval}\n\n`;
|
||||
let allMessage = `Detect Range: ${config.detectRange}\n`;
|
||||
allMessage += `Detect Interval: ${config.detectInterval}\n`;
|
||||
allMessage += `Warn Interval: ${config.watchInterval}\n\n`;
|
||||
allMessage +=
|
||||
"Use 'showconfig groups' or 'showconfig toast' for detailed view";
|
||||
|
||||
@@ -530,10 +552,9 @@ export class AccessControlCLI {
|
||||
const ev = pullEventAs(ChatBoxEvent, "chat");
|
||||
|
||||
if (ev === undefined) continue;
|
||||
if (
|
||||
!this.context.config.adminGroupConfig.groupUsers.includes(ev.username)
|
||||
)
|
||||
continue;
|
||||
|
||||
const config = loadConfig(this.context.configFilepath)!;
|
||||
if (!config.adminGroupConfig.groupUsers.includes(ev.username)) continue;
|
||||
if (!ev.message.startsWith("@AC")) continue;
|
||||
|
||||
this.context.log.info(
|
||||
@@ -542,7 +563,6 @@ export class AccessControlCLI {
|
||||
|
||||
const result = this.processor.processCommand(ev.message, ev.username);
|
||||
this.processor.sendResponse(result, ev.username);
|
||||
|
||||
if (!result.success) {
|
||||
this.context.log.warn(`Command failed: ${result.message}`);
|
||||
}
|
||||
|
||||
@@ -101,9 +101,13 @@ const defaultConfig: AccessConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
function loadConfig(filepath: string): AccessConfig {
|
||||
function loadConfig(
|
||||
filepath: string,
|
||||
useDefault = true,
|
||||
): AccessConfig | undefined {
|
||||
const [fp] = io.open(filepath, "r");
|
||||
if (fp == undefined) {
|
||||
if (useDefault === false) return undefined;
|
||||
print("Failed to open config file " + filepath);
|
||||
print("Use default config");
|
||||
saveConfig(defaultConfig, filepath);
|
||||
@@ -112,6 +116,7 @@ function loadConfig(filepath: string): AccessConfig {
|
||||
|
||||
const configJson = fp.read("*a");
|
||||
if (configJson == undefined) {
|
||||
if (useDefault === false) return undefined;
|
||||
print("Failed to read config file");
|
||||
print("Use default config");
|
||||
saveConfig(defaultConfig, filepath);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createAccessControlCLI } from "./cli";
|
||||
import { launchAccessControlTUI } from "./tui";
|
||||
import * as peripheralManager from "../lib/PeripheralManager";
|
||||
import { deepCopy } from "@/lib/common";
|
||||
import { ReadWriteLock } from "@/lib/ReadWriteLock";
|
||||
|
||||
const args = [...$vararg];
|
||||
|
||||
@@ -16,7 +17,8 @@ const logger = new CCLog("accesscontrol.log", {
|
||||
|
||||
// Load Config
|
||||
const configFilepath = `${shell.dir()}/access.config.json`;
|
||||
let config = loadConfig(configFilepath);
|
||||
let config = loadConfig(configFilepath)!;
|
||||
const configLock = new ReadWriteLock();
|
||||
logger.info("Load config successfully!");
|
||||
logger.debug(textutils.serialise(config, { allow_repetitions: true }));
|
||||
|
||||
@@ -25,7 +27,6 @@ const playerDetector = peripheralManager.findByNameRequired("playerDetector");
|
||||
const chatBox = peripheralManager.findByNameRequired("chatBox");
|
||||
|
||||
// Global
|
||||
let noticeTargetPlayers: string[];
|
||||
let inRangePlayers: string[] = [];
|
||||
let watchPlayersInfo: { name: string; hasNoticeTimes: number }[] = [];
|
||||
|
||||
@@ -36,9 +37,16 @@ interface ParseParams {
|
||||
}
|
||||
|
||||
function reloadConfig() {
|
||||
config = loadConfig(configFilepath);
|
||||
let releaser = configLock.tryAcquireWrite();
|
||||
while (releaser === undefined) {
|
||||
sleep(1);
|
||||
releaser = configLock.tryAcquireWrite();
|
||||
}
|
||||
|
||||
config = loadConfig(configFilepath)!;
|
||||
inRangePlayers = [];
|
||||
watchPlayersInfo = [];
|
||||
releaser.release();
|
||||
logger.info("Reload config successfully!");
|
||||
}
|
||||
|
||||
@@ -80,7 +88,13 @@ function sendToast(
|
||||
targetPlayer: string,
|
||||
params: ParseParams,
|
||||
) {
|
||||
return chatBox.sendFormattedToastToPlayer(
|
||||
let releaser = configLock.tryAcquireRead();
|
||||
while (releaser === undefined) {
|
||||
sleep(0.1);
|
||||
releaser = configLock.tryAcquireRead();
|
||||
}
|
||||
|
||||
chatBox.sendFormattedToastToPlayer(
|
||||
safeParseTextComponent(
|
||||
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
||||
params,
|
||||
@@ -96,11 +110,18 @@ function sendToast(
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
releaser.release();
|
||||
}
|
||||
|
||||
function sendNotice(player: string, playerInfo?: PlayerInfo) {
|
||||
let releaser = configLock.tryAcquireRead();
|
||||
while (releaser === undefined) {
|
||||
sleep(0.1);
|
||||
releaser = configLock.tryAcquireRead();
|
||||
}
|
||||
|
||||
const onlinePlayers = playerDetector.getOnlinePlayers();
|
||||
noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat(
|
||||
const noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat(
|
||||
config.usersGroups
|
||||
.filter((value) => value.isNotice)
|
||||
.map((value) => value.groupUsers ?? [])
|
||||
@@ -114,12 +135,19 @@ function sendNotice(player: string, playerInfo?: PlayerInfo) {
|
||||
info: playerInfo,
|
||||
});
|
||||
}
|
||||
releaser.release();
|
||||
}
|
||||
|
||||
function sendWarn(player: string) {
|
||||
const warnMsg = `Not Allowed Player ${player} Break in Home `;
|
||||
logger.warn(warnMsg);
|
||||
|
||||
let releaser = configLock.tryAcquireRead();
|
||||
while (releaser === undefined) {
|
||||
sleep(0.1);
|
||||
releaser = configLock.tryAcquireRead();
|
||||
}
|
||||
|
||||
sendToast(config.warnToastConfig, player, { name: player });
|
||||
chatBox.sendFormattedMessageToPlayer(
|
||||
safeParseTextComponent(config.warnToastConfig.msg, { name: player }),
|
||||
@@ -130,10 +158,17 @@ function sendWarn(player: string) {
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
releaser.release();
|
||||
}
|
||||
|
||||
function watchLoop() {
|
||||
while (true) {
|
||||
const releaser = configLock.tryAcquireRead();
|
||||
if (releaser === undefined) {
|
||||
os.sleep(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const watchPlayerNames = watchPlayersInfo.flatMap((value) => value.name);
|
||||
logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
|
||||
for (const player of watchPlayersInfo) {
|
||||
@@ -164,12 +199,19 @@ function watchLoop() {
|
||||
os.sleep(1);
|
||||
}
|
||||
|
||||
releaser.release();
|
||||
os.sleep(config.watchInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function mainLoop() {
|
||||
while (true) {
|
||||
const releaser = configLock.tryAcquireRead();
|
||||
if (releaser === undefined) {
|
||||
os.sleep(0.1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const players = playerDetector.getPlayersInRange(config.detectRange);
|
||||
const playersList = "[ " + players.join(",") + " ]";
|
||||
logger.debug(`Detected ${players.length} players: ${playersList}`);
|
||||
@@ -214,6 +256,7 @@ function mainLoop() {
|
||||
}
|
||||
|
||||
inRangePlayers = players;
|
||||
releaser.release();
|
||||
os.sleep(config.detectInterval);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +275,6 @@ function keyboardLoop() {
|
||||
} finally {
|
||||
logger.setInTerminal(true);
|
||||
reloadConfig();
|
||||
logger.info("Reload config successfully!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,7 +286,6 @@ function main(args: string[]) {
|
||||
if (args[0] == "start") {
|
||||
// 创建CLI处理器
|
||||
const cli = createAccessControlCLI({
|
||||
config: config,
|
||||
configFilepath: configFilepath,
|
||||
reloadConfig: () => reloadConfig(),
|
||||
log: logger,
|
||||
@@ -259,7 +300,7 @@ function main(args: string[]) {
|
||||
mainLoop();
|
||||
},
|
||||
() => {
|
||||
void cli.startConfigLoop();
|
||||
cli.startConfigLoop();
|
||||
},
|
||||
() => {
|
||||
watchLoop();
|
||||
@@ -268,6 +309,7 @@ function main(args: string[]) {
|
||||
keyboardLoop();
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
} else if (args[0] == "config") {
|
||||
logger.info("Launching Access Control TUI...");
|
||||
|
||||
@@ -49,7 +49,7 @@ interface ErrorState {
|
||||
const AccessControlTUI = () => {
|
||||
// Load configuration on initialization
|
||||
const configFilepath = `${shell.dir()}/access.config.json`;
|
||||
const loadedConfig = loadConfig(configFilepath);
|
||||
const loadedConfig = loadConfig(configFilepath)!;
|
||||
// Configuration state
|
||||
const [config, setConfig] = createStore<AccessConfig>(loadedConfig);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user