finish basic access control system

This commit is contained in:
2025-10-09 14:09:47 +08:00
parent d3cbc15450
commit 11d138751a
32 changed files with 4687 additions and 345 deletions

View File

@@ -0,0 +1,67 @@
{
"detectRange": 64,
"detectInterval": 1,
"warnInterval": 7,
"adminGroupConfig": {
"groupName": "Admin",
"groupUsers": ["Selcon"],
"isAllowed": true,
"isWarnTarget": true
},
"defaultToastConfig": {
"title": {
"text": "Welcome",
"color": "green"
},
"msg": {
"text": "Hello %groupName% %playerName%",
"color": "green"
},
"prefix": "桃花源",
"brackets": "[]",
"bracketColor": ""
},
"warnToastConfig": {
"title": {
"text": "Attention!!!",
"color": "red"
},
"msg": {
"text": "%playerName% you are not allowed to be here",
"color": "red"
},
"prefix": "Taohuayuan",
"brackets": "[]",
"bracketColor": ""
},
"usersGroups": [
{
"groupName": "user",
"groupUsers": [],
"isAllowed": true,
"isWarnTarget": true
},
{
"groupName": "VIP",
"groupUsers": [],
"isAllowed": true,
"isWarnTarget": false
},
{
"groupName": "enemy",
"groupUsers": [],
"isAllowed": false,
"isWarnTarget": false,
"toastConfig": {
"title": {
"text": "Warn",
"color": "red"
},
"msg": {
"text": "Warn %playerName%",
"color": "red"
}
}
}
]
}

0
src/accesscontrol/cli.ts Normal file
View File

158
src/accesscontrol/config.ts Normal file
View File

@@ -0,0 +1,158 @@
import { CCLog } from "@/lib/ccLog";
import * as dkjson from "@sikongjueluo/dkjson-types";
let log: CCLog | undefined;
interface ToastConfig {
title: MinecraftTextComponent;
msg: MinecraftTextComponent;
prefix?: string;
brackets?: string;
bracketColor?: string;
}
interface UserGroupConfig {
groupName: string;
isAllowed: boolean;
isWarnTarget: boolean;
groupUsers: string[];
toastConfig?: ToastConfig;
}
interface AccessConfig {
detectInterval: number;
warnInterval: number;
detectRange: number;
adminGroupConfig: UserGroupConfig;
defaultToastConfig: ToastConfig;
warnToastConfig: ToastConfig;
usersGroups: UserGroupConfig[];
}
const defaultConfig: AccessConfig = {
detectRange: 64,
detectInterval: 3,
warnInterval: 7,
adminGroupConfig: {
groupName: "Admin",
groupUsers: ["Selcon"],
isAllowed: true,
isWarnTarget: false,
},
usersGroups: [
{
groupName: "user",
groupUsers: [],
isAllowed: true,
isWarnTarget: true,
},
{
groupName: "VIP",
groupUsers: [],
isAllowed: true,
isWarnTarget: false,
},
{
groupName: "enemies",
groupUsers: [],
isAllowed: false,
isWarnTarget: false,
toastConfig: {
title: {
text: "Warn",
color: "red",
},
msg: {
text: "Warn %playerName%",
color: "red",
},
},
},
],
defaultToastConfig: {
title: {
text: "Welcome",
color: "green",
},
msg: {
text: "Hello User %playerName%",
color: "green",
},
prefix: "Taohuayuan",
brackets: "[]",
bracketColor: "",
},
warnToastConfig: {
title: {
text: "Attention!!!",
color: "red",
},
msg: {
text: "%playerName% you are not allowed to be here",
color: "red",
},
prefix: "Taohuayuan",
brackets: "[]",
bracketColor: "",
},
};
function setLog(newLog: CCLog) {
log = newLog;
}
function loadConfig(filepath: string): AccessConfig {
const [fp] = io.open(filepath, "r");
if (fp == undefined) {
print("Failed to open config file " + filepath);
return defaultConfig;
}
const configJson = fp.read("*a");
if (configJson == undefined) {
print("Failed to read config file");
return defaultConfig;
}
const [config, pos, err] = dkjson.decode(configJson);
if (config == undefined) {
log?.warn(
`Config decode failed at ${pos}, use default instead. Error :${err}`,
);
return defaultConfig;
}
// Not use external lib
// const config = textutils.unserialiseJSON(configJson, {
// parse_empty_array: true,
// });
return config as AccessConfig;
}
function saveConfig(config: AccessConfig, filepath: string) {
const configJson = dkjson.encode(config, { indent: true }) as string;
// Not use external lib
// const configJson = textutils.serializeJSON(config, { unicode_strings: true });
if (configJson == undefined) {
print("Failed to save config");
}
const [fp, _err] = io.open(filepath, "w+");
if (fp == undefined) {
print("Failed to open config file " + filepath);
return;
}
fp.write(configJson);
fp.close();
}
export {
ToastConfig,
UserGroupConfig,
AccessConfig,
loadConfig,
saveConfig,
setLog,
};

355
src/accesscontrol/main.ts Normal file
View File

@@ -0,0 +1,355 @@
import { CCLog, DAY } from "@/lib/ccLog";
import {
ToastConfig,
UserGroupConfig,
loadConfig,
saveConfig,
setLog,
} from "./config";
import * as peripheralManager from "../lib/PeripheralManager";
import { ChatBoxEvent, pullEventAs } from "@/lib/event";
import { quotestring } from "@sikongjueluo/dkjson-types";
const DEBUG = false;
const args = [...$vararg];
const log = new CCLog("accesscontrol.log", DAY);
setLog(log);
const configFilepath = `${shell.dir()}/access.config.json`;
const config = loadConfig(configFilepath);
log.info("Load config successfully!");
log.debug(textutils.serialise(config, { allow_repetitions: true }));
const groupNames = config.usersGroups.map((value) => value.groupName);
const warnTargetPlayers = config.adminGroupConfig.groupUsers.concat(
config.usersGroups
.filter((value) => value.isWarnTarget)
.map((value) => value.groupUsers ?? [])
.flat(),
);
const playerDetector = peripheralManager.findByNameRequired("playerDetector");
const chatBox = peripheralManager.findByNameRequired("chatBox");
let inRangePlayers: string[] = [];
let notAllowedPlayers: string[] = [];
function safeParseTextComponent(
component: MinecraftTextComponent,
playerName: string,
groupName?: string,
): string {
if (component.text == undefined) {
component.text = "Wrong text, please contanct with admin";
} else if (component.text.includes("%")) {
component.text = component.text.replace("%playerName%", playerName);
if (groupName != undefined)
component.text = component.text.replace("%groupName%", groupName);
}
return textutils.serialiseJSON(component);
}
function sendToast(
toastConfig: ToastConfig,
player: string,
groupConfig?: UserGroupConfig,
) {
return chatBox.sendFormattedToastToPlayer(
safeParseTextComponent(
toastConfig.msg ?? config.defaultToastConfig.msg,
player,
groupConfig?.groupName,
),
safeParseTextComponent(
toastConfig.title ?? config.defaultToastConfig.title,
player,
groupConfig?.groupName,
),
player,
quotestring(toastConfig.prefix ?? config.defaultToastConfig.prefix!),
toastConfig.brackets ?? config.defaultToastConfig.brackets,
toastConfig.bracketColor ?? config.defaultToastConfig.bracketColor,
undefined,
true,
);
}
function sendWarn(player: string) {
const playerPos = playerDetector.getPlayerPos(player);
const onlinePlayers = playerDetector.getOnlinePlayers();
const warnMsg = `Not Allowed Player ${player} Break in Home at Position ${playerPos?.x}, ${playerPos?.y}, ${playerPos?.z}`;
log.warn(warnMsg);
sendToast(config.warnToastConfig, player);
chatBox.sendFormattedMessageToPlayer(
safeParseTextComponent(config.warnToastConfig.msg, player),
player,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
for (const targetPlayer of warnTargetPlayers) {
if (!onlinePlayers.includes(targetPlayer)) continue;
chatBox.sendFormattedMessageToPlayer(
textutils.serialise({
text: warnMsg,
color: "red",
} as MinecraftTextComponent),
targetPlayer,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
}
}
function sendCommandHelp(targetPlayer: string) {
chatBox.sendMessageToPlayer(
`
Command Usage: @AC /<Command> [args]
Command:
- add <userGroup> <playerName>
add player to group
userGroup: ${groupNames.join(", ")}
- del <userGroup> <playerName>
delete player in the group, except Admin
userGroup: ${groupNames.join(", ")}
- list
list all of the player with its group
- set <options> [params]
config access control settins
options:
- warnInterval <number>
set the interval of warn, which is not allowed
- detectInterval <number>
set the interval of detecting players
- detectRange <number>
set the sphere range of detect
`,
targetPlayer,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
}
function warnLoop() {
while (true) {
for (const player of notAllowedPlayers) {
if (inRangePlayers.includes(player)) {
sendWarn(player);
} else {
notAllowedPlayers = notAllowedPlayers.filter(
(value) => value != player,
);
}
}
os.sleep(config.warnInterval);
}
}
function mainLoop() {
while (true) {
const players = playerDetector.getPlayersInRange(config.detectRange);
if (DEBUG) {
const playersList = "[ " + players.join(",") + " ]";
log.debug(`Detected ${players.length} players: ${playersList}`);
}
for (const player of players) {
if (inRangePlayers.includes(player)) continue;
if (config.adminGroupConfig.groupUsers.includes(player)) {
log.info(`Admin ${player} enter`);
sendToast(
config.adminGroupConfig.toastConfig ?? config.defaultToastConfig,
player,
config.adminGroupConfig,
);
continue;
}
let inUserGroup = false;
for (const userGroupConfig of config.usersGroups) {
if (userGroupConfig.groupUsers == undefined) continue;
if (!userGroupConfig.groupUsers.includes(player)) continue;
if (!userGroupConfig.isAllowed) {
sendWarn(player);
notAllowedPlayers.push(player);
continue;
}
log.info(`${userGroupConfig.groupName} ${player} enter`);
sendToast(
userGroupConfig.toastConfig ?? config.defaultToastConfig,
player,
userGroupConfig,
);
inUserGroup = true;
}
if (inUserGroup) continue;
sendWarn(player);
notAllowedPlayers.push(player);
}
inRangePlayers = players;
os.sleep(config.detectInterval);
}
}
function configLoop() {
while (true) {
const ev = pullEventAs(ChatBoxEvent, "chat");
if (ev == undefined) continue;
if (!config.adminGroupConfig.groupUsers.includes(ev.username)) continue;
if (!ev.message.startsWith("@AC")) continue;
// log.info(`Received "${ev.message}" from admin ${ev.username}`);
const params = ev.message.split(" ");
if (params.length < 2) {
sendCommandHelp(ev.username);
continue;
}
if (params[1] == "/add" && params.length == 4) {
if (params[2] == "admin") {
config.adminGroupConfig.groupUsers.push(params[3]);
chatBox.sendMessageToPlayer(
`Add player ${params[3]} to admin`,
ev.username,
"AccessControl",
);
} else if (groupNames.includes(params[2])) {
const groupConfig = config.usersGroups.find(
(value) => value.groupName == params[2],
)!;
if (groupConfig.groupUsers == undefined)
groupConfig.groupUsers = [params[3]];
else groupConfig.groupUsers.push(params[3]);
chatBox.sendMessageToPlayer(
`Add player ${params[3]} to ${groupConfig.groupName}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else if (params[1] == "/del" && params.length == 4) {
if (params[2] == "admin") {
chatBox.sendMessageToPlayer(
`Could't delete admin, please edit config`,
ev.username,
"AccessControl",
);
} else if (groupNames.includes(params[2])) {
const groupConfig = config.usersGroups.find(
(value) => value.groupName == params[2],
)!;
if (groupConfig.groupUsers == undefined) groupConfig.groupUsers = [];
else
groupConfig.groupUsers = groupConfig.groupUsers.filter(
(user) => user != params[3],
);
chatBox.sendMessageToPlayer(
`Delete ${groupConfig.groupName} ${params[3]}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else if (params[1] == "/list") {
chatBox.sendMessageToPlayer(
`Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]`,
ev.username,
"AccessControl",
);
for (const groupConfig of config.usersGroups) {
chatBox.sendMessageToPlayer(
`${groupConfig.groupName} : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]`,
ev.username,
"AccessControl",
);
}
} else if (params[1] == "/set" && params.length == 4) {
if (params[2] == "warnInterval") {
config.warnInterval = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set warn interval to ${config.warnInterval}`,
ev.username,
"AccessControl",
);
} else if (params[2] == "detectInterval") {
config.detectInterval = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set detect interval to ${config.detectInterval}`,
ev.username,
"AccessControl",
);
} else if (params[2] == "detectRange") {
config.detectRange = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set detect range to ${config.detectRange}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else {
sendCommandHelp(ev.username);
continue;
}
saveConfig(config, configFilepath);
}
}
function main(args: string[]) {
log.debug("Starting access control system, get args: " + args.join(", "));
if (args.length == 1) {
if (args[0] == "start") {
parallel.waitForAll(
() => {
mainLoop();
},
() => {
configLoop();
},
() => {
warnLoop();
},
);
return;
}
}
print(`Usage: accesscontrol start`);
}
try {
main(args);
} catch (error: unknown) {
log.error(textutils.serialise(error as object));
} finally {
log.close();
}