fix: wrong type, chat manager unicode string; feature: accesscontrol welcome message and chinese support

This commit is contained in:
2025-11-02 21:04:12 +08:00
parent f76a3666b1
commit 7e03d960bd
7 changed files with 311 additions and 145 deletions

View File

@@ -14,7 +14,9 @@ export interface AppContext {
configFilepath: string; configFilepath: string;
reloadConfig: () => void; reloadConfig: () => void;
logger: CCLog; logger: CCLog;
print: (message: string) => void; print: (
message: string | MinecraftTextComponent | MinecraftTextComponent[],
) => void;
} }
function getGroupNames(config: AccessConfig) { function getGroupNames(config: AccessConfig) {
@@ -25,14 +27,14 @@ function getGroupNames(config: AccessConfig) {
const addCommand: Command<AppContext> = { const addCommand: Command<AppContext> = {
name: "add", name: "add",
description: "Add player to group", description: "添加玩家到用户组",
args: [ args: [
{ {
name: "userGroup", name: "userGroup",
description: "Group to add player to", description: "要添加到的用户组",
required: true, required: true,
}, },
{ name: "playerName", description: "Player to add", required: true }, { name: "playerName", description: "要添加的玩家", required: true },
], ],
action: ({ args, context }) => { action: ({ args, context }) => {
const [groupName, playerName] = [ const [groupName, playerName] = [
@@ -49,9 +51,11 @@ const addCommand: Command<AppContext> = {
const group = config.usersGroups.find((g) => g.groupName === groupName); const group = config.usersGroups.find((g) => g.groupName === groupName);
if (!group) { if (!group) {
const groupNames = getGroupNames(config); const groupNames = getGroupNames(config);
context.print( context.print({
`Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`, text: `无效的用户组: ${groupName}. 可用用户组: ${groupNames.join(
); ", ",
)}`,
});
return Ok.EMPTY; return Ok.EMPTY;
} }
group.groupUsers ??= []; group.groupUsers ??= [];
@@ -62,21 +66,21 @@ const addCommand: Command<AppContext> = {
saveConfig(config, context.configFilepath); saveConfig(config, context.configFilepath);
context.reloadConfig(); context.reloadConfig();
context.print(`Added player ${playerName} to ${groupName}`); context.print({ text: `已添加玩家 ${playerName} ${groupName}` });
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };
const delCommand: Command<AppContext> = { const delCommand: Command<AppContext> = {
name: "del", name: "del",
description: "Delete player from group", description: "从用户组删除玩家",
args: [ args: [
{ {
name: "userGroup", name: "userGroup",
description: "Group to delete player from", description: "要从中删除玩家的用户组",
required: true, required: true,
}, },
{ name: "playerName", description: "Player to delete", required: true }, { name: "playerName", description: "要删除的玩家", required: true },
], ],
action: ({ args, context }) => { action: ({ args, context }) => {
const [groupName, playerName] = [ const [groupName, playerName] = [
@@ -85,7 +89,7 @@ const delCommand: Command<AppContext> = {
]; ];
if (groupName === "admin") { if (groupName === "admin") {
context.print("Could not delete admin, please edit config file."); context.print({ text: "无法删除管理员, 请直接编辑配置文件。" });
return Ok.EMPTY; return Ok.EMPTY;
} }
@@ -94,9 +98,11 @@ const delCommand: Command<AppContext> = {
if (!group) { if (!group) {
const groupNames = getGroupNames(config); const groupNames = getGroupNames(config);
context.print( context.print({
`Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`, text: `无效的用户组: ${groupName}. 可用用户组: ${groupNames.join(
); ", ",
)}`,
});
return Ok.EMPTY; return Ok.EMPTY;
} }
@@ -106,43 +112,45 @@ const delCommand: Command<AppContext> = {
saveConfig(config, context.configFilepath); saveConfig(config, context.configFilepath);
context.reloadConfig(); context.reloadConfig();
context.print(`Deleted player ${playerName} from ${groupName}`); context.print({ text: `已从 ${groupName} 中删除玩家 ${playerName}` });
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };
const listCommand: Command<AppContext> = { const listCommand: Command<AppContext> = {
name: "list", name: "list",
description: "List all players with their groups", description: "列出所有玩家及其所在的用户组",
action: ({ context }) => { action: ({ context }) => {
const config = loadConfig(context.configFilepath)!; const config = loadConfig(context.configFilepath)!;
let message = `Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]\n`; let message = `管理员 : [ ${config.adminGroupConfig.groupUsers.join(
", ",
)} ]\n`;
for (const groupConfig of config.usersGroups) { for (const groupConfig of config.usersGroups) {
const users = groupConfig.groupUsers ?? []; const users = groupConfig.groupUsers ?? [];
message += `${groupConfig.groupName} : [ ${users.join(", ")} ]\n`; message += `${groupConfig.groupName} : [ ${users.join(", ")} ]\n`;
} }
context.print(message.trim()); context.print({ text: message.trim() });
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };
const setCommand: Command<AppContext> = { const setCommand: Command<AppContext> = {
name: "set", name: "set",
description: "Config access control settings", description: "配置访问控制设置",
args: [ args: [
{ {
name: "option", name: "option",
description: "Option to set (warnInterval, detectInterval, detectRange)", description: "要设置的选项 (warnInterval, detectInterval, detectRange)",
required: true, required: true,
}, },
{ name: "value", description: "Value to set", required: true }, { name: "value", description: "要设置的值", required: true },
], ],
action: ({ args, context }) => { action: ({ args, context }) => {
const [option, valueStr] = [args.option as string, args.value as string]; const [option, valueStr] = [args.option as string, args.value as string];
const value = parseInt(valueStr); const value = parseInt(valueStr);
if (isNaN(value)) { if (isNaN(value)) {
context.print(`Invalid value: ${valueStr}. Must be a number.`); context.print({ text: `无效的值: ${valueStr}. 必须是一个数字。` });
return Ok.EMPTY; return Ok.EMPTY;
} }
@@ -152,45 +160,45 @@ const setCommand: Command<AppContext> = {
switch (option) { switch (option) {
case "warnInterval": case "warnInterval":
config.watchInterval = value; config.watchInterval = value;
message = `Set warn interval to ${value}`; message = `已设置警告间隔为 ${value}`;
break; break;
case "detectInterval": case "detectInterval":
config.detectInterval = value; config.detectInterval = value;
message = `Set detect interval to ${value}`; message = `已设置检测间隔为 ${value}`;
break; break;
case "detectRange": case "detectRange":
config.detectRange = value; config.detectRange = value;
message = `Set detect range to ${value}`; message = `已设置检测范围为 ${value}`;
break; break;
default: default:
context.print( context.print({
`Unknown option: ${option}. Available: warnInterval, detectInterval, detectRange`, text: `未知选项: ${option}. 可用选项: warnInterval, detectInterval, detectRange`,
); });
return Ok.EMPTY; return Ok.EMPTY;
} }
saveConfig(config, context.configFilepath); saveConfig(config, context.configFilepath);
context.reloadConfig(); context.reloadConfig();
context.print(message); context.print({ text: message });
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };
const editGroupCommand: Command<AppContext> = { const editGroupCommand: Command<AppContext> = {
name: "group", name: "group",
description: "Edit group properties", description: "编辑用户组属性",
args: [ args: [
{ {
name: "groupName", name: "groupName",
description: "Name of the group to edit", description: "要编辑的用户组名称",
required: true, required: true,
}, },
{ {
name: "property", name: "property",
description: "Property to change (isAllowed, isNotice)", description: "要更改的属性 (isAllowed, isNotice)",
required: true, required: true,
}, },
{ name: "value", description: "New value (true/false)", required: true }, { name: "value", description: "新值 (true/false)", required: true },
], ],
action: ({ args, context }) => { action: ({ args, context }) => {
const [groupName, property, valueStr] = [ const [groupName, property, valueStr] = [
@@ -208,15 +216,15 @@ const editGroupCommand: Command<AppContext> = {
} }
if (!groupConfig) { if (!groupConfig) {
context.print(`Group ${groupName} not found`); context.print({ text: `用户组 ${groupName} 未找到` });
return Ok.EMPTY; return Ok.EMPTY;
} }
const boolValue = parseBoolean(valueStr); const boolValue = parseBoolean(valueStr);
if (boolValue === undefined) { if (boolValue === undefined) {
context.print( context.print({
`Invalid boolean value: ${valueStr}. Use 'true' or 'false'.`, text: `无效的布尔值: ${valueStr}. 请使用 'true' 'false'.`,
); });
return Ok.EMPTY; return Ok.EMPTY;
} }
@@ -224,63 +232,65 @@ const editGroupCommand: Command<AppContext> = {
switch (property) { switch (property) {
case "isAllowed": case "isAllowed":
groupConfig.isAllowed = boolValue; groupConfig.isAllowed = boolValue;
message = `Set ${groupName}.isAllowed to ${boolValue}`; message = `已设置 ${groupName}.isAllowed ${boolValue}`;
break; break;
case "isNotice": case "isNotice":
groupConfig.isNotice = boolValue; groupConfig.isNotice = boolValue;
message = `Set ${groupName}.isNotice to ${boolValue}`; message = `已设置 ${groupName}.isNotice ${boolValue}`;
break; break;
default: default:
context.print( context.print({
`Unknown property: ${property}. Available: isAllowed, isNotice`, text: `未知属性: ${property}. 可用属性: isAllowed, isNotice`,
); });
return Ok.EMPTY; return Ok.EMPTY;
} }
saveConfig(config, context.configFilepath); saveConfig(config, context.configFilepath);
context.reloadConfig(); context.reloadConfig();
context.print(message); context.print({ text: message });
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };
const editCommand: Command<AppContext> = { const editCommand: Command<AppContext> = {
name: "edit", name: "edit",
description: "Edit various configurations", description: "编辑各项配置",
subcommands: new Map([["group", editGroupCommand]]), subcommands: new Map([["group", editGroupCommand]]),
}; };
const showConfigCommand: Command<AppContext> = { const showConfigCommand: Command<AppContext> = {
name: "showconfig", name: "showconfig",
description: "Show configuration", description: "显示配置",
options: new Map([ options: new Map([
[ [
"type", "type",
{ {
name: "type", name: "type",
description: "Type of config to show (groups, toast, all)", description: "要显示的配置类型 (groups, toast, all)",
required: false, required: false,
defaultValue: "all", defaultValue: "all",
}, },
], ],
]), ]),
action: ({ args, context }) => { action: ({ options, context }) => {
const type = args.type as string; const type = options.type as string;
const config = loadConfig(context.configFilepath)!; const config = loadConfig(context.configFilepath)!;
let message = ""; let message = "";
switch (type) { switch (type) {
case "groups": { case "groups": {
let groupsMessage = `Admin Group: ${config.adminGroupConfig.groupName}\n`; let groupsMessage = `管理员组: ${config.adminGroupConfig.groupName}\n`;
groupsMessage += ` Users: [${config.adminGroupConfig.groupUsers.join(", ")}]\n`; groupsMessage += ` 用户: [${config.adminGroupConfig.groupUsers.join(
groupsMessage += ` Allowed: ${config.adminGroupConfig.isAllowed}\n`; ", ",
groupsMessage += ` notice: ${config.adminGroupConfig.isNotice}\n\n`; )}]\n`;
groupsMessage += ` 允许: ${config.adminGroupConfig.isAllowed}\n`;
groupsMessage += ` 通知: ${config.adminGroupConfig.isNotice}\n\n`;
for (const group of config.usersGroups) { for (const group of config.usersGroups) {
groupsMessage += `Group: ${group.groupName}\n`; groupsMessage += `用户组: ${group.groupName}\n`;
groupsMessage += ` Users: [${(group.groupUsers ?? []).join(", ")}]\n`; groupsMessage += ` 用户: [${(group.groupUsers ?? []).join(", ")}]\n`;
groupsMessage += ` Allowed: ${group.isAllowed}\n`; groupsMessage += ` 允许: ${group.isAllowed}\n`;
groupsMessage += ` Notice: ${group.isNotice}\n`; groupsMessage += ` 通知: ${group.isNotice}\n`;
groupsMessage += "\n"; groupsMessage += "\n";
} }
message = groupsMessage.trim(); message = groupsMessage.trim();
@@ -288,38 +298,48 @@ const showConfigCommand: Command<AppContext> = {
} }
case "toast": { case "toast": {
let toastMessage = "Default Toast Config:\n"; let toastMessage = "默认 Toast 配置:\n";
toastMessage += ` Title: ${config.welcomeToastConfig.title.text}\n`; toastMessage += ` 标题: ${config.welcomeToastConfig.title.text}\n`;
toastMessage += ` Message: ${config.welcomeToastConfig.msg.text}\n`; toastMessage += ` 消息: ${config.welcomeToastConfig.msg.text}\n`;
toastMessage += ` Prefix: ${config.welcomeToastConfig.prefix ?? "none"}\n`; toastMessage += ` 前缀: ${
toastMessage += ` Brackets: ${config.welcomeToastConfig.brackets ?? "none"}\n`; config.welcomeToastConfig.prefix ?? "none"
toastMessage += ` Bracket Color: ${config.welcomeToastConfig.bracketColor ?? "none"}\n\n`; }\n`;
toastMessage += ` 括号: ${
config.welcomeToastConfig.brackets ?? "none"
}\n`;
toastMessage += ` 括号颜色: ${
config.welcomeToastConfig.bracketColor ?? "none"
}\n\n`;
toastMessage += "Warn Toast Config:\n"; toastMessage += "警告 Toast 配置:\n";
toastMessage += ` Title: ${config.warnToastConfig.title.text}\n`; toastMessage += ` 标题: ${config.warnToastConfig.title.text}\n`;
toastMessage += ` Message: ${config.warnToastConfig.msg.text}\n`; toastMessage += ` 消息: ${config.warnToastConfig.msg.text}\n`;
toastMessage += ` Prefix: ${config.warnToastConfig.prefix ?? "none"}\n`; toastMessage += ` 前缀: ${config.warnToastConfig.prefix ?? "none"}\n`;
toastMessage += ` Brackets: ${config.warnToastConfig.brackets ?? "none"}\n`; toastMessage += ` 括号: ${
toastMessage += ` Bracket Color: ${config.warnToastConfig.bracketColor ?? "none"}`; config.warnToastConfig.brackets ?? "none"
}\n`;
toastMessage += ` 括号颜色: ${
config.warnToastConfig.bracketColor ?? "none"
}`;
message = toastMessage; message = toastMessage;
break; break;
} }
case "all": { case "all": {
let allMessage = `Detect Range: ${config.detectRange}\n`; let allMessage = `检测范围: ${config.detectRange}\n`;
allMessage += `Detect Interval: ${config.detectInterval}\n`; allMessage += `检测间隔: ${config.detectInterval}\n`;
allMessage += `Warn Interval: ${config.watchInterval}\n\n`; allMessage += `警告间隔: ${config.watchInterval}\n\n`;
allMessage += allMessage +=
"Use 'showconfig groups' or 'showconfig toast' for detailed view"; "使用 'showconfig --type groups' 'showconfig --type toast' 查看详细信息";
message = allMessage; message = allMessage;
break; break;
} }
default: default:
message = `Invalid type: ${type}. Available: groups, toast, all`; message = `无效类型: ${type}. 可用类型: groups, toast, all`;
break; break;
} }
context.print(message); context.print({ text: message });
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };
@@ -327,7 +347,7 @@ const showConfigCommand: Command<AppContext> = {
// Root command // Root command
const rootCommand: Command<AppContext> = { const rootCommand: Command<AppContext> = {
name: "@AC", name: "@AC",
description: "Access Control command line interface", description: "访问控制命令行界面",
subcommands: new Map([ subcommands: new Map([
["add", addCommand], ["add", addCommand],
["del", delCommand], ["del", delCommand],
@@ -337,7 +357,25 @@ const rootCommand: Command<AppContext> = {
["showconfig", showConfigCommand], ["showconfig", showConfigCommand],
]), ]),
action: ({ context }) => { action: ({ context }) => {
context.print("Welcome to Access Control CLI"); context.print([
{
text: "请使用 ",
},
{
text: "@AC --help",
clickEvent: {
action: "copy_to_clipboard",
value: "@AC --help",
},
hoverEvent: {
action: "show_text",
value: "点击复制命令",
},
},
{
text: " 获取门禁系统更详细的命令说明😊😊😊",
},
]);
return Ok.EMPTY; return Ok.EMPTY;
}, },
}; };

View File

@@ -12,6 +12,7 @@ interface UserGroupConfig {
groupName: string; groupName: string;
isAllowed: boolean; isAllowed: boolean;
isNotice: boolean; isNotice: boolean;
isWelcome: boolean;
groupUsers: string[]; groupUsers: string[];
} }
@@ -39,6 +40,7 @@ const defaultConfig: AccessConfig = {
groupUsers: ["Selcon"], groupUsers: ["Selcon"],
isAllowed: true, isAllowed: true,
isNotice: true, isNotice: true,
isWelcome: true,
}, },
usersGroups: [ usersGroups: [
{ {
@@ -46,57 +48,60 @@ const defaultConfig: AccessConfig = {
groupUsers: [], groupUsers: [],
isAllowed: true, isAllowed: true,
isNotice: true, isNotice: true,
isWelcome: false,
}, },
{ {
groupName: "VIP", groupName: "VIP",
groupUsers: [], groupUsers: [],
isAllowed: true, isAllowed: true,
isNotice: false, isNotice: false,
isWelcome: true,
}, },
{ {
groupName: "enemies", groupName: "enemies",
groupUsers: [], groupUsers: [],
isAllowed: false, isAllowed: false,
isNotice: false, isNotice: false,
isWelcome: false,
}, },
], ],
welcomeToastConfig: { welcomeToastConfig: {
title: { title: {
text: "Welcome", text: "欢迎",
color: "green", color: "green",
}, },
msg: { msg: {
text: "Hello User %playerName%", text: "欢迎 %playerName% 参观桃源星喵~",
color: "green", color: "#EDC8DA",
}, },
prefix: "Taohuayuan", prefix: "桃源星",
brackets: "[]", brackets: "<>",
bracketColor: "", bracketColor: "",
}, },
noticeToastConfig: { noticeToastConfig: {
title: { title: {
text: "Notice", text: "警告",
color: "red", color: "red",
}, },
msg: { msg: {
text: "Unfamiliar player %playerName% appeared at Position %playerPosX%, %playerPosY%, %playerPosZ%", text: "陌生玩家 %playerName% 出现在 %playerPosX%, %playerPosY%, %playerPosZ%",
color: "red", color: "red",
}, },
prefix: "Taohuayuan", prefix: "桃源星",
brackets: "[]", brackets: "<>",
bracketColor: "", bracketColor: "",
}, },
warnToastConfig: { warnToastConfig: {
title: { title: {
text: "Attention!!!", text: "注意",
color: "red", color: "red",
}, },
msg: { msg: {
text: "%playerName% you are not allowed to be here", text: "%playerName% 你已经进入桃源星领地",
color: "red", color: "red",
}, },
prefix: "Taohuayuan", prefix: "桃源星",
brackets: "[]", brackets: "<>",
bracketColor: "", bracketColor: "",
}, },
}; };

View File

@@ -7,6 +7,7 @@ import { deepCopy } from "@/lib/common";
import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock"; import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
import { ChatManager } from "@/lib/ChatManager"; import { ChatManager } from "@/lib/ChatManager";
import { gTimerManager } from "@/lib/TimerManager"; import { gTimerManager } from "@/lib/TimerManager";
import { KeyEvent, pullEventAs } from "@/lib/event";
const args = [...$vararg]; const args = [...$vararg];
@@ -25,17 +26,20 @@ logger.info("Load config successfully!");
logger.debug(textutils.serialise(config, { allow_repetitions: true })); logger.debug(textutils.serialise(config, { allow_repetitions: true }));
// Peripheral // Peripheral
const playerDetector = peripheralManager.findByNameRequired("playerDetector"); const playerDetector = peripheral.find(
const chatBox = peripheralManager.findByNameRequired("chatBox"); "playerDetector",
)[0] as PlayerDetectorPeripheral;
const chatBox = peripheral.find("chatBox")[0] as ChatBoxPeripheral;
const chatManager: ChatManager = new ChatManager([chatBox]); const chatManager: ChatManager = new ChatManager([chatBox]);
// Global // Global
let inRangePlayers: string[] = []; let gInRangePlayers: string[] = [];
let watchPlayersInfo: { name: string; hasNoticeTimes: number }[] = []; let gWatchPlayersInfo: { name: string; hasNoticeTimes: number }[] = [];
let gIsRunning = true;
interface ParseParams { interface ParseParams {
name?: string; playerName?: string;
group?: string; groupName?: string;
info?: PlayerInfo; info?: PlayerInfo;
} }
@@ -47,8 +51,8 @@ function reloadConfig() {
} }
config = loadConfig(configFilepath)!; config = loadConfig(configFilepath)!;
inRangePlayers = []; gInRangePlayers = [];
watchPlayersInfo = []; gWatchPlayersInfo = [];
releaser.release(); releaser.release();
logger.info("Reload config successfully!"); logger.info("Reload config successfully!");
} }
@@ -56,7 +60,7 @@ function reloadConfig() {
function safeParseTextComponent( function safeParseTextComponent(
component: MinecraftTextComponent, component: MinecraftTextComponent,
params?: ParseParams, params?: ParseParams,
): string { ): MinecraftTextComponent {
const newComponent = deepCopy(component); const newComponent = deepCopy(component);
if (newComponent.text == undefined) { if (newComponent.text == undefined) {
@@ -64,11 +68,11 @@ function safeParseTextComponent(
} else if (newComponent.text.includes("%")) { } else if (newComponent.text.includes("%")) {
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%playerName%", "%playerName%",
params?.name ?? "UnknowPlayer", params?.playerName ?? "UnknowPlayer",
); );
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%groupName%", "%groupName%",
params?.group ?? "UnknowGroup", params?.groupName ?? "UnknowGroup",
); );
newComponent.text = newComponent.text.replace( newComponent.text = newComponent.text.replace(
"%playerPosX%", "%playerPosX%",
@@ -83,7 +87,34 @@ function safeParseTextComponent(
params?.info?.z.toString() ?? "UnknowPosZ", params?.info?.z.toString() ?? "UnknowPosZ",
); );
} }
return textutils.serialiseJSON(newComponent); return newComponent;
}
function sendMessage(
toastConfig: ToastConfig,
targetPlayer: string,
params: ParseParams,
) {
let releaser = configLock.tryAcquireRead();
while (releaser === undefined) {
sleep(0.1);
releaser = configLock.tryAcquireRead();
}
chatManager.sendMessage({
message: safeParseTextComponent(
toastConfig.msg ?? config.welcomeToastConfig.msg,
params,
),
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
bracketColor:
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
targetPlayer: targetPlayer,
utf8Support: true,
});
releaser.release();
} }
function sendToast( function sendToast(
@@ -134,7 +165,7 @@ function sendNotice(player: string, playerInfo?: PlayerInfo) {
for (const targetPlayer of noticeTargetPlayers) { for (const targetPlayer of noticeTargetPlayers) {
if (!onlinePlayers.includes(targetPlayer)) continue; if (!onlinePlayers.includes(targetPlayer)) continue;
sendToast(config.noticeToastConfig, targetPlayer, { sendToast(config.noticeToastConfig, targetPlayer, {
name: player, playerName: player,
info: playerInfo, info: playerInfo,
}); });
sleep(1); sleep(1);
@@ -152,10 +183,10 @@ function sendWarn(player: string) {
releaser = configLock.tryAcquireRead(); releaser = configLock.tryAcquireRead();
} }
sendToast(config.warnToastConfig, player, { name: player }); sendToast(config.warnToastConfig, player, { playerName: player });
chatManager.sendMessage({ chatManager.sendMessage({
message: safeParseTextComponent(config.warnToastConfig.msg, { message: safeParseTextComponent(config.warnToastConfig.msg, {
name: player, playerName: player,
}), }),
targetPlayer: player, targetPlayer: player,
prefix: "AccessControl", prefix: "AccessControl",
@@ -166,18 +197,18 @@ function sendWarn(player: string) {
} }
function watchLoop() { function watchLoop() {
while (true) { while (gIsRunning) {
const releaser = configLock.tryAcquireRead(); const releaser = configLock.tryAcquireRead();
if (releaser === undefined) { if (releaser === undefined) {
os.sleep(1); os.sleep(1);
continue; continue;
} }
const watchPlayerNames = watchPlayersInfo.flatMap((value) => value.name); const watchPlayerNames = gWatchPlayersInfo.flatMap((value) => value.name);
logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`); logger.debug(`Watch Players [ ${watchPlayerNames.join(", ")} ]`);
for (const player of watchPlayersInfo) { for (const player of gWatchPlayersInfo) {
const playerInfo = playerDetector.getPlayerPos(player.name); const playerInfo = playerDetector.getPlayerPos(player.name);
if (inRangePlayers.includes(player.name)) { if (gInRangePlayers.includes(player.name)) {
// Notice // Notice
if (player.hasNoticeTimes < config.noticeTimes) { if (player.hasNoticeTimes < config.noticeTimes) {
sendNotice(player.name, playerInfo); sendNotice(player.name, playerInfo);
@@ -193,7 +224,7 @@ function watchLoop() {
); );
} else { } else {
// Get rid of player from list // Get rid of player from list
watchPlayersInfo = watchPlayersInfo.filter( gWatchPlayersInfo = gWatchPlayersInfo.filter(
(value) => value.name != player.name, (value) => value.name != player.name,
); );
logger.info( logger.info(
@@ -209,7 +240,7 @@ function watchLoop() {
} }
function mainLoop() { function mainLoop() {
while (true) { while (gIsRunning) {
const releaser = configLock.tryAcquireRead(); const releaser = configLock.tryAcquireRead();
if (releaser === undefined) { if (releaser === undefined) {
os.sleep(0.1); os.sleep(0.1);
@@ -221,20 +252,31 @@ function mainLoop() {
logger.debug(`Detected ${players.length} players: ${playersList}`); logger.debug(`Detected ${players.length} players: ${playersList}`);
for (const player of players) { for (const player of players) {
if (inRangePlayers.includes(player)) continue; if (gInRangePlayers.includes(player)) continue;
// Get player Info
const playerInfo = playerDetector.getPlayerPos(player);
if (config.adminGroupConfig.groupUsers.includes(player)) { if (config.adminGroupConfig.groupUsers.includes(player)) {
logger.info(`Admin ${player} appear`); logger.info(
`Admin ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
);
if (config.adminGroupConfig.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: "Admin",
info: playerInfo,
});
continue; continue;
} }
// New player appear // New player appear
const playerInfo = playerDetector.getPlayerPos(player);
let groupConfig: UserGroupConfig = { let groupConfig: UserGroupConfig = {
groupName: "Unfamiliar", groupName: "Unfamiliar",
groupUsers: [], groupUsers: [],
isAllowed: false, isAllowed: false,
isNotice: false, isNotice: false,
isWelcome: false,
}; };
for (const userGroupConfig of config.usersGroups) { for (const userGroupConfig of config.usersGroups) {
if (userGroupConfig.groupUsers == undefined) continue; if (userGroupConfig.groupUsers == undefined) continue;
@@ -247,28 +289,37 @@ function mainLoop() {
break; break;
} }
if (config.adminGroupConfig.isWelcome)
sendMessage(config.welcomeToastConfig, player, {
playerName: player,
groupName: groupConfig.groupName,
info: playerInfo,
});
if (groupConfig.isAllowed) continue; if (groupConfig.isAllowed) continue;
logger.warn( logger.warn(
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`, `${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
); );
if (config.isWarn) sendWarn(player); if (config.isWarn) sendWarn(player);
watchPlayersInfo = [ gWatchPlayersInfo = [
...watchPlayersInfo, ...gWatchPlayersInfo,
{ name: player, hasNoticeTimes: 0 }, { name: player, hasNoticeTimes: 0 },
]; ];
} }
inRangePlayers = players; gInRangePlayers = players;
releaser.release(); releaser.release();
os.sleep(config.detectInterval); os.sleep(config.detectInterval);
} }
} }
function keyboardLoop() { function keyboardLoop() {
while (true) { while (gIsRunning) {
const [eventType, key] = os.pullEvent("key"); const event = pullEventAs(KeyEvent, "key");
if (eventType === "key" && key === keys.c) { if (event === undefined) continue;
if (event.key === keys.c) {
logger.info("Launching Access Control TUI..."); logger.info("Launching Access Control TUI...");
try { try {
logger.setInTerminal(false); logger.setInTerminal(false);
@@ -280,7 +331,12 @@ function keyboardLoop() {
logger.setInTerminal(true); logger.setInTerminal(true);
reloadConfig(); reloadConfig();
} }
} else if (event.key === keys.r) {
reloadConfig();
} }
// else if (event.key === keys.q) {
// gIsRunning = false;
// }
} }
} }
@@ -300,7 +356,7 @@ function cliLoop() {
}), }),
}); });
while (true) { while (gIsRunning) {
const result = chatManager.getReceivedMessage(); const result = chatManager.getReceivedMessage();
if (result.isErr()) { if (result.isErr()) {
sleep(0.5); sleep(0.5);
@@ -342,9 +398,11 @@ function main(args: string[]) {
logger.info("Starting access control system, get args: " + args.join(", ")); logger.info("Starting access control system, get args: " + args.join(", "));
if (args.length == 1) { if (args.length == 1) {
if (args[0] == "start") { if (args[0] == "start") {
print( const tutorial: string[] = [];
"Access Control System started. Press 'c' to open configuration TUI.", tutorial.push("Access Control System started.");
); tutorial.push("\tPress 'c' to open configuration TUI.");
tutorial.push("\tPress 'r' to reload configuration.");
print(tutorial.join("\n"));
parallel.waitForAll( parallel.waitForAll(
() => mainLoop(), () => mainLoop(),
() => gTimerManager.run(), () => gTimerManager.run(),

View File

@@ -355,6 +355,34 @@ const AccessControlTUI = () => {
{ class: "flex flex-col ml-2" }, { class: "flex flex-col ml-2" },
label({}, () => `Group: ${getSelectedGroup().groupName}`), label({}, () => `Group: ${getSelectedGroup().groupName}`),
div(
{ class: "flex flex-row" },
label({}, "Is Welcome:"),
input({
type: "checkbox",
checked: () => getSelectedGroup().isWelcome,
onChange: (checked) => {
const groupIndex = selectedGroupIndex();
if (groupIndex === 0) {
const currentAdmin = config().adminGroupConfig;
setConfig("adminGroupConfig", {
...currentAdmin,
isWelcome: checked,
});
} else {
const actualIndex = groupIndex - 1;
const currentGroups = config().usersGroups;
const currentGroup = currentGroups[actualIndex];
const newGroups = [...currentGroups];
newGroups[actualIndex] = {
...currentGroup,
isWelcome: checked,
};
setConfig("usersGroups", newGroups);
}
},
}),
),
div( div(
{ class: "flex flex-row" }, { class: "flex flex-row" },
label({}, "Is Allowed:"), label({}, "Is Allowed:"),
@@ -541,7 +569,12 @@ const AccessControlTUI = () => {
label({}, "Prefix:"), label({}, "Prefix:"),
input({ input({
type: "text", type: "text",
value: () => getTempToastConfig().prefix, value: () => {
const str = textutils.serialiseJSON(getTempToastConfig().prefix, {
unicode_strings: true,
});
return str.substring(1, str.length - 1);
},
onInput: (value) => onInput: (value) =>
setTempToastConfig({ ...getTempToastConfig(), prefix: value }), setTempToastConfig({ ...getTempToastConfig(), prefix: value }),
onFocusChanged: () => { onFocusChanged: () => {

View File

@@ -53,7 +53,7 @@ export interface ChatToast extends ChatBasicMessage {
/** Target player username to send the toast to */ /** Target player username to send the toast to */
targetPlayer: string; targetPlayer: string;
/** Title of the toast notification */ /** Title of the toast notification */
title: string; title: string | MinecraftTextComponent | MinecraftTextComponent[];
} }
/** /**
@@ -224,9 +224,13 @@ export class ChatManager {
// Send private message to specific player // Send private message to specific player
if (typeof message.message === "string") { if (typeof message.message === "string") {
[success, errorMsg] = chatbox.sendMessageToPlayer( [success, errorMsg] = chatbox.sendMessageToPlayer(
message.message, textutils.serialiseJSON(message.message, {
unicode_strings: message.utf8Support,
}),
message.targetPlayer, message.targetPlayer,
message.prefix, textutils.serialiseJSON(message.prefix ?? "AP", {
unicode_strings: message.utf8Support,
}),
message.brackets, message.brackets,
message.bracketColor, message.bracketColor,
message.range, message.range,
@@ -236,11 +240,13 @@ export class ChatManager {
// Handle MinecraftTextComponent for private message // Handle MinecraftTextComponent for private message
[success, errorMsg] = chatbox.sendFormattedMessageToPlayer( [success, errorMsg] = chatbox.sendFormattedMessageToPlayer(
textutils.serialiseJSON(message.message, { textutils.serialiseJSON(message.message, {
unicode_strings: true, unicode_strings: message.utf8Support,
allow_repetitions: true, allow_repetitions: true,
}), }),
message.targetPlayer, message.targetPlayer,
message.prefix, textutils.serialiseJSON(message.prefix ?? "AP", {
unicode_strings: message.utf8Support,
}),
message.brackets, message.brackets,
message.bracketColor, message.bracketColor,
message.range, message.range,
@@ -251,8 +257,12 @@ export class ChatManager {
// Send global message // Send global message
if (typeof message.message === "string") { if (typeof message.message === "string") {
[success, errorMsg] = chatbox.sendMessage( [success, errorMsg] = chatbox.sendMessage(
message.message, textutils.serialiseJSON(message.message, {
message.prefix, unicode_strings: message.utf8Support,
}),
textutils.serialiseJSON(message.prefix ?? "AP", {
unicode_strings: message.utf8Support,
}),
message.brackets, message.brackets,
message.bracketColor, message.bracketColor,
message.range, message.range,
@@ -262,10 +272,12 @@ export class ChatManager {
// Handle MinecraftTextComponent for global message // Handle MinecraftTextComponent for global message
[success, errorMsg] = chatbox.sendFormattedMessage( [success, errorMsg] = chatbox.sendFormattedMessage(
textutils.serialiseJSON(message.message, { textutils.serialiseJSON(message.message, {
unicode_strings: true, unicode_strings: message.utf8Support,
allow_repetitions: true, allow_repetitions: true,
}), }),
message.prefix, textutils.serialiseJSON(message.prefix ?? "AP", {
unicode_strings: message.utf8Support,
}),
message.brackets, message.brackets,
message.bracketColor, message.bracketColor,
message.range, message.range,
@@ -321,10 +333,16 @@ export class ChatManager {
typeof toast.title === "string" typeof toast.title === "string"
) { ) {
[success, errorMsg] = chatbox.sendToastToPlayer( [success, errorMsg] = chatbox.sendToastToPlayer(
toast.message, textutils.serialiseJSON(toast.message, {
toast.title, unicode_strings: toast.utf8Support,
}),
textutils.serialiseJSON(toast.title, {
unicode_strings: toast.utf8Support,
}),
toast.targetPlayer, toast.targetPlayer,
toast.prefix, textutils.serialiseJSON(toast.prefix ?? "AP", {
unicode_strings: toast.utf8Support,
}),
toast.brackets, toast.brackets,
toast.bracketColor, toast.bracketColor,
toast.range, toast.range,
@@ -337,21 +355,23 @@ export class ChatManager {
? toast.message ? toast.message
: textutils.serialiseJSON(toast.message, { : textutils.serialiseJSON(toast.message, {
unicode_strings: true, unicode_strings: true,
allow_repetitions: true, allow_repetitions: toast.utf8Support,
}); });
const titleJson = const titleJson =
typeof toast.title === "string" typeof toast.title === "string"
? toast.title ? toast.title
: textutils.serialiseJSON(toast.title, { : textutils.serialiseJSON(toast.title, {
unicode_strings: true, unicode_strings: true,
allow_repetitions: true, allow_repetitions: toast.utf8Support,
}); });
[success, errorMsg] = chatbox.sendFormattedToastToPlayer( [success, errorMsg] = chatbox.sendFormattedToastToPlayer(
messageJson, messageJson,
titleJson, titleJson,
toast.targetPlayer, toast.targetPlayer,
toast.prefix, textutils.serialiseJSON(toast.prefix ?? "AP", {
unicode_strings: toast.utf8Support,
}),
toast.brackets, toast.brackets,
toast.bracketColor, toast.bracketColor,
toast.range, toast.range,

View File

@@ -30,7 +30,7 @@ declare type MinecraftColor =
| "light_purple" | "light_purple"
| "yellow" | "yellow"
| "white" | "white"
| "reset"; // RGB color in #RRGGBB format | `#${string}`;
declare type MinecraftFont = declare type MinecraftFont =
| "minecraft:default" | "minecraft:default"

View File

@@ -925,10 +925,22 @@ declare namespace textutils {
function pagedTabulate(...args: (LuaTable | Object | Color)[]): void; function pagedTabulate(...args: (LuaTable | Object | Color)[]): void;
function serialize(tab: object, options?: SerializeOptions): string; function serialize(tab: object, options?: SerializeOptions): string;
function serialise(tab: object, options?: SerializeOptions): string; function serialise(tab: object, options?: SerializeOptions): string;
function serializeJSON(tab: object, nbtStyle?: boolean): string; function serializeJSON(
function serializeJSON(tab: object, options: SerializeJSONOptions): string; tab: object | string | number | boolean,
function serialiseJSON(tab: object, nbtStyle?: boolean): string; nbtStyle?: boolean,
function serialiseJSON(tab: object, options: SerializeJSONOptions): string; ): string;
function serializeJSON(
tab: object | string | number | boolean,
options: SerializeJSONOptions,
): string;
function serialiseJSON(
tab: object | string | number | boolean,
nbtStyle?: boolean,
): string;
function serialiseJSON(
tab: object | string | number | boolean,
options: SerializeJSONOptions,
): string;
function unserialize(str: string): unknown; function unserialize(str: string): unknown;
function unserialise(str: string): unknown; function unserialise(str: string): unknown;
function unserializeJSON( function unserializeJSON(