mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-05 03:37:50 +08:00
feature: global timer manager; reconstruct: accesscontrol; fix: chat manager
feature: - add global timer manager reconstruct: - use new cli framework for accesscontrol - use chat manager for accesscontrol fix: - chat manager only send one time
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { Command, createCli } from "@/lib/ccCLI";
|
||||||
|
import { Ok } from "@/lib/thirdparty/ts-result-es";
|
||||||
import { CCLog } from "@/lib/ccLog";
|
import { CCLog } from "@/lib/ccLog";
|
||||||
import {
|
import {
|
||||||
AccessConfig,
|
AccessConfig,
|
||||||
@@ -5,473 +7,267 @@ import {
|
|||||||
loadConfig,
|
loadConfig,
|
||||||
saveConfig,
|
saveConfig,
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import { ChatBoxEvent, pullEventAs } from "@/lib/event";
|
|
||||||
import { parseBoolean } from "@/lib/common";
|
import { parseBoolean } from "@/lib/common";
|
||||||
|
|
||||||
// CLI命令接口
|
// 1. Define AppContext
|
||||||
interface CLICommand {
|
export interface AppContext {
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
usage: string;
|
|
||||||
execute: (args: string[], executor: string, context: CLIContext) => CLIResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI执行结果
|
|
||||||
interface CLIResult {
|
|
||||||
success: boolean;
|
|
||||||
message?: string;
|
|
||||||
shouldSaveConfig?: boolean;
|
|
||||||
config?: AccessConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI上下文
|
|
||||||
interface CLIContext {
|
|
||||||
configFilepath: string;
|
configFilepath: string;
|
||||||
reloadConfig: () => void;
|
reloadConfig: () => void;
|
||||||
log: CCLog;
|
logger: CCLog;
|
||||||
chatBox: ChatBoxPeripheral;
|
print: (message: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGroupNames(config: AccessConfig) {
|
function getGroupNames(config: AccessConfig) {
|
||||||
return config.usersGroups.flatMap((value) => value.groupName);
|
return config.usersGroups.map((value) => value.groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基础命令处理器
|
// 2. Define Commands
|
||||||
class CLICommandProcessor {
|
|
||||||
private commands = new Map<string, CLICommand>();
|
|
||||||
private context: CLIContext;
|
|
||||||
|
|
||||||
constructor(context: CLIContext) {
|
const addCommand: Command<AppContext> = {
|
||||||
this.context = context;
|
name: "add",
|
||||||
this.initializeCommands();
|
description: "Add player to group",
|
||||||
}
|
args: [
|
||||||
|
{
|
||||||
private initializeCommands() {
|
name: "userGroup",
|
||||||
// 注册所有命令
|
description: "Group to add player to",
|
||||||
this.registerCommand(new AddCommand());
|
required: true,
|
||||||
this.registerCommand(new DelCommand());
|
},
|
||||||
this.registerCommand(new ListCommand());
|
{ name: "playerName", description: "Player to add", required: true },
|
||||||
this.registerCommand(new SetCommand());
|
],
|
||||||
this.registerCommand(new EditCommand());
|
action: ({ args, context }) => {
|
||||||
this.registerCommand(new ShowConfigCommand());
|
const [groupName, playerName] = [
|
||||||
this.registerCommand(new HelpCommand());
|
args.userGroup as string,
|
||||||
}
|
args.playerName as string,
|
||||||
|
];
|
||||||
private registerCommand(command: CLICommand) {
|
const config = loadConfig(context.configFilepath)!;
|
||||||
this.commands.set(command.name, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public processCommand(message: string, executor: string): CLIResult {
|
|
||||||
const params = message.split(" ");
|
|
||||||
|
|
||||||
// 移除 "@AC" 前缀
|
|
||||||
if (params.length < 2) {
|
|
||||||
return this.getHelpCommand().execute([], executor, this.context);
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandName = params[1].replace("/", ""); // 移除 "/" 前缀
|
|
||||||
const args = params.slice(2);
|
|
||||||
|
|
||||||
const command = this.commands.get(commandName);
|
|
||||||
if (!command) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Unknown command: ${commandName}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret = command.execute(args, executor, this.context);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getHelpCommand(): CLICommand {
|
|
||||||
return this.commands.get("help")!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sendResponse(result: CLIResult, executor: string) {
|
|
||||||
if (result.message != null && result.message.length > 0) {
|
|
||||||
this.context.chatBox.sendMessageToPlayer(
|
|
||||||
result.message,
|
|
||||||
executor,
|
|
||||||
"AccessControl",
|
|
||||||
"[]",
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.shouldSaveConfig === true) {
|
|
||||||
saveConfig(result.config!, this.context.configFilepath);
|
|
||||||
this.context.reloadConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加用户命令
|
|
||||||
class AddCommand implements CLICommand {
|
|
||||||
name = "add";
|
|
||||||
description = "Add player to group";
|
|
||||||
usage = "add <userGroup> <playerName>";
|
|
||||||
|
|
||||||
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
|
|
||||||
if (args.length !== 2) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Usage: ${this.usage}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [groupName, playerName] = args;
|
|
||||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
|
||||||
|
|
||||||
if (groupName === "admin") {
|
if (groupName === "admin") {
|
||||||
|
if (!config.adminGroupConfig.groupUsers.includes(playerName)) {
|
||||||
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(config);
|
|
||||||
|
|
||||||
if (!groupNames.includes(groupName)) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(
|
|
||||||
", ",
|
|
||||||
)}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupConfig = config.usersGroups.find(
|
|
||||||
(value) => value.groupName === groupName,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!groupConfig) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Group ${groupName} not found`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupConfig.groupUsers === undefined) {
|
|
||||||
groupConfig.groupUsers = [playerName];
|
|
||||||
} else {
|
} else {
|
||||||
groupConfig.groupUsers.push(playerName);
|
const group = config.usersGroups.find((g) => g.groupName === groupName);
|
||||||
|
if (!group) {
|
||||||
|
const groupNames = getGroupNames(config);
|
||||||
|
context.print(
|
||||||
|
`Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`,
|
||||||
|
);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
group.groupUsers ??= [];
|
||||||
|
if (!group.groupUsers.includes(playerName)) {
|
||||||
|
group.groupUsers.push(playerName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
saveConfig(config, context.configFilepath);
|
||||||
success: true,
|
context.reloadConfig();
|
||||||
message: `Add player ${playerName} to ${groupConfig.groupName}`,
|
context.print(`Added player ${playerName} to ${groupName}`);
|
||||||
shouldSaveConfig: true,
|
return Ok.EMPTY;
|
||||||
config,
|
},
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除用户命令
|
const delCommand: Command<AppContext> = {
|
||||||
class DelCommand implements CLICommand {
|
name: "del",
|
||||||
name = "del";
|
description: "Delete player from group",
|
||||||
description = "Delete player from group";
|
args: [
|
||||||
usage = "del <userGroup> <playerName>";
|
{
|
||||||
|
name: "userGroup",
|
||||||
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
|
description: "Group to delete player from",
|
||||||
if (args.length !== 2) {
|
required: true,
|
||||||
return {
|
},
|
||||||
success: false,
|
{ name: "playerName", description: "Player to delete", required: true },
|
||||||
message: `Usage: ${this.usage}`,
|
],
|
||||||
};
|
action: ({ args, context }) => {
|
||||||
}
|
const [groupName, playerName] = [
|
||||||
|
args.userGroup as string,
|
||||||
const [groupName, playerName] = args;
|
args.playerName as string,
|
||||||
|
];
|
||||||
|
|
||||||
if (groupName === "admin") {
|
if (groupName === "admin") {
|
||||||
return {
|
context.print("Could not delete admin, please edit config file.");
|
||||||
success: false,
|
return Ok.EMPTY;
|
||||||
message: "Could't delete admin, please edit config",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
const group = config.usersGroups.find((g) => g.groupName === groupName);
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
const groupNames = getGroupNames(config);
|
const groupNames = getGroupNames(config);
|
||||||
|
context.print(
|
||||||
if (!groupNames.includes(groupName)) {
|
`Invalid group: ${groupName}. Available groups: ${groupNames.join(", ")}`,
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Invalid group: ${groupName}. Available groups: ${groupNames.join(
|
|
||||||
", ",
|
|
||||||
)}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupConfig = config.usersGroups.find(
|
|
||||||
(value) => value.groupName === groupName,
|
|
||||||
);
|
);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
if (!groupConfig) {
|
if (group.groupUsers !== undefined) {
|
||||||
return {
|
group.groupUsers = group.groupUsers.filter((user) => user !== playerName);
|
||||||
success: false,
|
}
|
||||||
message: `Group ${groupName} not found`,
|
|
||||||
|
saveConfig(config, context.configFilepath);
|
||||||
|
context.reloadConfig();
|
||||||
|
context.print(`Deleted player ${playerName} from ${groupName}`);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
if (groupConfig.groupUsers === undefined) {
|
const listCommand: Command<AppContext> = {
|
||||||
groupConfig.groupUsers = [];
|
name: "list",
|
||||||
} else {
|
description: "List all players with their groups",
|
||||||
groupConfig.groupUsers = groupConfig.groupUsers.filter(
|
action: ({ context }) => {
|
||||||
(user) => user !== playerName,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: `Delete ${groupConfig.groupName} ${playerName}`,
|
|
||||||
shouldSaveConfig: true,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 列表命令
|
|
||||||
class ListCommand implements CLICommand {
|
|
||||||
name = "list";
|
|
||||||
description = "List all players with their groups";
|
|
||||||
usage = "list";
|
|
||||||
|
|
||||||
execute(_args: string[], _executor: string, context: CLIContext): CLIResult {
|
|
||||||
const config = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
let message = `Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]\n`;
|
let message = `Admins : [ ${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());
|
||||||
return {
|
return Ok.EMPTY;
|
||||||
success: true,
|
},
|
||||||
message: message.trim(),
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置命令
|
const setCommand: Command<AppContext> = {
|
||||||
class SetCommand implements CLICommand {
|
name: "set",
|
||||||
name = "set";
|
description: "Config access control settings",
|
||||||
description = "Config access control settings";
|
args: [
|
||||||
usage = "set <option> <value>";
|
{
|
||||||
|
name: "option",
|
||||||
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
|
description: "Option to set (warnInterval, detectInterval, detectRange)",
|
||||||
if (args.length !== 2) {
|
required: true,
|
||||||
return {
|
},
|
||||||
success: false,
|
{ name: "value", description: "Value to set", required: true },
|
||||||
message: `Usage: ${this.usage}\nOptions: warnInterval, detectInterval, detectRange`,
|
],
|
||||||
};
|
action: ({ args, context }) => {
|
||||||
}
|
const [option, valueStr] = [args.option as string, args.value as string];
|
||||||
|
|
||||||
const [option, valueStr] = args;
|
|
||||||
const value = parseInt(valueStr);
|
const value = parseInt(valueStr);
|
||||||
|
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
return {
|
context.print(`Invalid value: ${valueStr}. Must be a number.`);
|
||||||
success: false,
|
return Ok.EMPTY;
|
||||||
message: `Invalid value: ${valueStr}. Must be a number.`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
let message = "";
|
||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case "warnInterval":
|
case "warnInterval":
|
||||||
config.watchInterval = value;
|
config.watchInterval = value;
|
||||||
return {
|
message = `Set warn interval to ${value}`;
|
||||||
success: true,
|
break;
|
||||||
message: `Set warn interval to ${config.watchInterval}`,
|
|
||||||
shouldSaveConfig: true,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "detectInterval":
|
case "detectInterval":
|
||||||
config.detectInterval = value;
|
config.detectInterval = value;
|
||||||
return {
|
message = `Set detect interval to ${value}`;
|
||||||
success: true,
|
break;
|
||||||
message: `Set detect interval to ${config.detectInterval}`,
|
|
||||||
shouldSaveConfig: true,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
case "detectRange":
|
case "detectRange":
|
||||||
config.detectRange = value;
|
config.detectRange = value;
|
||||||
return {
|
message = `Set detect range to ${value}`;
|
||||||
success: true,
|
break;
|
||||||
message: `Set detect range to ${config.detectRange}`,
|
|
||||||
shouldSaveConfig: true,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {
|
context.print(
|
||||||
success: false,
|
`Unknown option: ${option}. Available: warnInterval, detectInterval, detectRange`,
|
||||||
message: `Unknown option: ${option}. Available options: warnInterval, detectInterval, detectRange`,
|
);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveConfig(config, context.configFilepath);
|
||||||
|
context.reloadConfig();
|
||||||
|
context.print(message);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 帮助命令
|
const editGroupCommand: Command<AppContext> = {
|
||||||
class HelpCommand implements CLICommand {
|
name: "group",
|
||||||
name = "help";
|
description: "Edit group properties",
|
||||||
description = "Show command help";
|
args: [
|
||||||
usage = "help";
|
{
|
||||||
|
name: "groupName",
|
||||||
execute(_args: string[], _executor: string, context: CLIContext): CLIResult {
|
description: "Name of the group to edit",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "property",
|
||||||
|
description: "Property to change (isAllowed, isNotice)",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{ name: "value", description: "New value (true/false)", required: true },
|
||||||
|
],
|
||||||
|
action: ({ args, context }) => {
|
||||||
|
const [groupName, property, valueStr] = [
|
||||||
|
args.groupName as string,
|
||||||
|
args.property as string,
|
||||||
|
args.value as string,
|
||||||
|
];
|
||||||
const config = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
const groupNames = getGroupNames(config);
|
|
||||||
const helpMessage = `
|
|
||||||
Command Usage: @AC /<Command> [args]
|
|
||||||
Commands:
|
|
||||||
- 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 settings
|
|
||||||
options: warnInterval, detectInterval, detectRange
|
|
||||||
- edit <target> [args]
|
|
||||||
edit various configurations
|
|
||||||
targets: group (edit group properties)
|
|
||||||
examples: edit group <groupName> <property> <value> (properties: isAllowed, isNotice)
|
|
||||||
- showconfig [type]
|
|
||||||
show configuration (type: groups/toast/all)
|
|
||||||
- help
|
|
||||||
show this help message
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: helpMessage.trim(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 统一编辑命令
|
|
||||||
class EditCommand implements CLICommand {
|
|
||||||
name = "edit";
|
|
||||||
description = "Edit various configurations (only group now)";
|
|
||||||
usage = "edit <target> [args]";
|
|
||||||
|
|
||||||
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
|
|
||||||
if (args.length < 1) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Usage: ${this.usage}\nTargets: group`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [target, ...rest] = args;
|
|
||||||
|
|
||||||
switch (target) {
|
|
||||||
case "group":
|
|
||||||
return this.editGroup(rest, context);
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Unknown target: ${target}. Available: group`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private editGroup(args: string[], context: CLIContext): CLIResult {
|
|
||||||
if (args.length !== 3) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Usage: edit group <groupName> <property> <value>\nProperties: isAllowed, isNotice`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const [groupName, property, valueStr] = args;
|
|
||||||
const config: AccessConfig = loadConfig(context.configFilepath)!;
|
|
||||||
|
|
||||||
let groupConfig: UserGroupConfig | undefined;
|
let groupConfig: UserGroupConfig | undefined;
|
||||||
|
|
||||||
if (groupName === "admin") {
|
if (groupName === "admin") {
|
||||||
groupConfig = config.adminGroupConfig;
|
groupConfig = config.adminGroupConfig;
|
||||||
} else {
|
} else {
|
||||||
groupConfig = config.usersGroups.find(
|
groupConfig = config.usersGroups.find((g) => g.groupName === groupName);
|
||||||
(group) => group.groupName === groupName,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!groupConfig) {
|
if (!groupConfig) {
|
||||||
return {
|
context.print(`Group ${groupName} not found`);
|
||||||
success: false,
|
return Ok.EMPTY;
|
||||||
message: `Group ${groupName} not found`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const boolValue = parseBoolean(valueStr);
|
||||||
|
if (boolValue === undefined) {
|
||||||
|
context.print(
|
||||||
|
`Invalid boolean value: ${valueStr}. Use 'true' or 'false'.`,
|
||||||
|
);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = "";
|
||||||
switch (property) {
|
switch (property) {
|
||||||
case "isAllowed": {
|
case "isAllowed":
|
||||||
const val = parseBoolean(valueStr);
|
groupConfig.isAllowed = boolValue;
|
||||||
if (val != undefined) {
|
message = `Set ${groupName}.isAllowed to ${boolValue}`;
|
||||||
groupConfig.isAllowed = val;
|
break;
|
||||||
return {
|
case "isNotice":
|
||||||
success: true,
|
groupConfig.isNotice = boolValue;
|
||||||
message: `Set ${groupName}.isAllowed to ${groupConfig.isAllowed}`,
|
message = `Set ${groupName}.isNotice to ${boolValue}`;
|
||||||
shouldSaveConfig: true,
|
break;
|
||||||
config,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Set ${groupName}.isAllowed failed`,
|
|
||||||
shouldSaveConfig: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "isNotice": {
|
|
||||||
const val = parseBoolean(valueStr);
|
|
||||||
if (val != undefined) {
|
|
||||||
groupConfig.isNotice = val;
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: `Set ${groupName}.isNotice to ${groupConfig.isNotice}`,
|
|
||||||
shouldSaveConfig: true,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Set ${groupName}.isAllowed failed`,
|
|
||||||
shouldSaveConfig: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {
|
context.print(
|
||||||
success: false,
|
`Unknown property: ${property}. Available: isAllowed, isNotice`,
|
||||||
message: `Unknown property: ${property}. Available: isAllowed, isNotice`,
|
);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveConfig(config, context.configFilepath);
|
||||||
|
context.reloadConfig();
|
||||||
|
context.print(message);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示配置命令
|
const editCommand: Command<AppContext> = {
|
||||||
class ShowConfigCommand implements CLICommand {
|
name: "edit",
|
||||||
name = "showconfig";
|
description: "Edit various configurations",
|
||||||
description = "Show configuration";
|
subcommands: new Map([["group", editGroupCommand]]),
|
||||||
usage = "showconfig [type]";
|
};
|
||||||
|
|
||||||
execute(args: string[], _executor: string, context: CLIContext): CLIResult {
|
const showConfigCommand: Command<AppContext> = {
|
||||||
const type = args[0] || "all";
|
name: "showconfig",
|
||||||
|
description: "Show configuration",
|
||||||
|
options: new Map([
|
||||||
|
[
|
||||||
|
"type",
|
||||||
|
{
|
||||||
|
name: "type",
|
||||||
|
description: "Type of config to show (groups, toast, all)",
|
||||||
|
required: false,
|
||||||
|
defaultValue: "all",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
action: ({ args, context }) => {
|
||||||
|
const type = args.type as string;
|
||||||
const config = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
let message = "";
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "groups": {
|
case "groups": {
|
||||||
@@ -487,11 +283,8 @@ class ShowConfigCommand implements CLICommand {
|
|||||||
groupsMessage += ` Notice: ${group.isNotice}\n`;
|
groupsMessage += ` Notice: ${group.isNotice}\n`;
|
||||||
groupsMessage += "\n";
|
groupsMessage += "\n";
|
||||||
}
|
}
|
||||||
|
message = groupsMessage.trim();
|
||||||
return {
|
break;
|
||||||
success: true,
|
|
||||||
message: groupsMessage.trim(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "toast": {
|
case "toast": {
|
||||||
@@ -508,11 +301,8 @@ class ShowConfigCommand implements CLICommand {
|
|||||||
toastMessage += ` Prefix: ${config.warnToastConfig.prefix ?? "none"}\n`;
|
toastMessage += ` Prefix: ${config.warnToastConfig.prefix ?? "none"}\n`;
|
||||||
toastMessage += ` Brackets: ${config.warnToastConfig.brackets ?? "none"}\n`;
|
toastMessage += ` Brackets: ${config.warnToastConfig.brackets ?? "none"}\n`;
|
||||||
toastMessage += ` Bracket Color: ${config.warnToastConfig.bracketColor ?? "none"}`;
|
toastMessage += ` Bracket Color: ${config.warnToastConfig.bracketColor ?? "none"}`;
|
||||||
|
message = toastMessage;
|
||||||
return {
|
break;
|
||||||
success: true,
|
|
||||||
message: toastMessage,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "all": {
|
case "all": {
|
||||||
@@ -521,58 +311,40 @@ class ShowConfigCommand implements CLICommand {
|
|||||||
allMessage += `Warn Interval: ${config.watchInterval}\n\n`;
|
allMessage += `Warn Interval: ${config.watchInterval}\n\n`;
|
||||||
allMessage +=
|
allMessage +=
|
||||||
"Use 'showconfig groups' or 'showconfig toast' for detailed view";
|
"Use 'showconfig groups' or 'showconfig toast' for detailed view";
|
||||||
|
message = allMessage;
|
||||||
return {
|
break;
|
||||||
success: true,
|
|
||||||
message: allMessage,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {
|
message = `Invalid type: ${type}. Available: groups, toast, all`;
|
||||||
success: false,
|
break;
|
||||||
message: `Invalid type: ${type}. Available: groups, toast, all`,
|
}
|
||||||
|
context.print(message);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI循环处理器
|
// Root command
|
||||||
export class AccessControlCLI {
|
const rootCommand: Command<AppContext> = {
|
||||||
private processor: CLICommandProcessor;
|
name: "@AC",
|
||||||
private context: CLIContext;
|
description: "Access Control command line interface",
|
||||||
|
subcommands: new Map([
|
||||||
|
["add", addCommand],
|
||||||
|
["del", delCommand],
|
||||||
|
["list", listCommand],
|
||||||
|
["set", setCommand],
|
||||||
|
["edit", editCommand],
|
||||||
|
["showconfig", showConfigCommand],
|
||||||
|
]),
|
||||||
|
action: ({ context }) => {
|
||||||
|
context.print("Welcome to Access Control CLI");
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
constructor(context: CLIContext) {
|
export function createAccessControlCli(context: AppContext) {
|
||||||
this.context = context;
|
return createCli(rootCommand, {
|
||||||
this.processor = new CLICommandProcessor(context);
|
globalContext: context,
|
||||||
}
|
writer: (msg) => context.print(msg),
|
||||||
|
});
|
||||||
public startConfigLoop() {
|
|
||||||
while (true) {
|
|
||||||
const ev = pullEventAs(ChatBoxEvent, "chat");
|
|
||||||
|
|
||||||
if (ev === undefined) 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(
|
|
||||||
`Received command "${ev.message}" from admin ${ev.username}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出类型和工厂函数
|
|
||||||
export { CLIContext, CLIResult, CLICommand };
|
|
||||||
|
|
||||||
export function createAccessControlCLI(context: CLIContext): AccessControlCLI {
|
|
||||||
return new AccessControlCLI(context);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { CCLog, DAY, LogLevel } from "@/lib/ccLog";
|
import { CCLog, DAY, LogLevel } from "@/lib/ccLog";
|
||||||
import { ToastConfig, UserGroupConfig, loadConfig } from "./config";
|
import { ToastConfig, UserGroupConfig, loadConfig } from "./config";
|
||||||
import { createAccessControlCLI } from "./cli";
|
import { createAccessControlCli } from "./cli";
|
||||||
import { launchAccessControlTUI } from "./tui";
|
import { launchAccessControlTUI } from "./tui";
|
||||||
import * as peripheralManager from "../lib/PeripheralManager";
|
import * as peripheralManager from "../lib/PeripheralManager";
|
||||||
import { deepCopy } from "@/lib/common";
|
import { deepCopy } from "@/lib/common";
|
||||||
import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
|
import { ReadWriteLock } from "@/lib/mutex/ReadWriteLock";
|
||||||
|
import { ChatManager } from "@/lib/ChatManager";
|
||||||
|
import { gTimerManager } from "@/lib/TimerManager";
|
||||||
|
|
||||||
const args = [...$vararg];
|
const args = [...$vararg];
|
||||||
|
|
||||||
@@ -12,7 +14,7 @@ const args = [...$vararg];
|
|||||||
const logger = new CCLog("accesscontrol.log", {
|
const logger = new CCLog("accesscontrol.log", {
|
||||||
printTerminal: true,
|
printTerminal: true,
|
||||||
logInterval: DAY,
|
logInterval: DAY,
|
||||||
outputMinLevel: LogLevel.Info,
|
outputMinLevel: LogLevel.Debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load Config
|
// Load Config
|
||||||
@@ -25,6 +27,7 @@ logger.debug(textutils.serialise(config, { allow_repetitions: true }));
|
|||||||
// Peripheral
|
// Peripheral
|
||||||
const playerDetector = peripheralManager.findByNameRequired("playerDetector");
|
const playerDetector = peripheralManager.findByNameRequired("playerDetector");
|
||||||
const chatBox = peripheralManager.findByNameRequired("chatBox");
|
const chatBox = peripheralManager.findByNameRequired("chatBox");
|
||||||
|
const chatManager: ChatManager = new ChatManager([chatBox]);
|
||||||
|
|
||||||
// Global
|
// Global
|
||||||
let inRangePlayers: string[] = [];
|
let inRangePlayers: string[] = [];
|
||||||
@@ -94,22 +97,22 @@ function sendToast(
|
|||||||
releaser = configLock.tryAcquireRead();
|
releaser = configLock.tryAcquireRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
chatBox.sendFormattedToastToPlayer(
|
chatManager.sendToast({
|
||||||
safeParseTextComponent(
|
message: safeParseTextComponent(
|
||||||
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
toastConfig.msg ?? config.welcomeToastConfig.msg,
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
safeParseTextComponent(
|
title: safeParseTextComponent(
|
||||||
toastConfig.title ?? config.welcomeToastConfig.title,
|
toastConfig.title ?? config.welcomeToastConfig.title,
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
targetPlayer,
|
prefix: toastConfig.prefix ?? config.welcomeToastConfig.prefix,
|
||||||
toastConfig.prefix ?? config.welcomeToastConfig.prefix,
|
brackets: toastConfig.brackets ?? config.welcomeToastConfig.brackets,
|
||||||
toastConfig.brackets ?? config.welcomeToastConfig.brackets,
|
bracketColor:
|
||||||
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
|
toastConfig.bracketColor ?? config.welcomeToastConfig.bracketColor,
|
||||||
undefined,
|
targetPlayer: targetPlayer,
|
||||||
true,
|
utf8Support: true,
|
||||||
);
|
});
|
||||||
releaser.release();
|
releaser.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,15 +153,15 @@ function sendWarn(player: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendToast(config.warnToastConfig, player, { name: player });
|
sendToast(config.warnToastConfig, player, { name: player });
|
||||||
chatBox.sendFormattedMessageToPlayer(
|
chatManager.sendMessage({
|
||||||
safeParseTextComponent(config.warnToastConfig.msg, { name: player }),
|
message: safeParseTextComponent(config.warnToastConfig.msg, {
|
||||||
player,
|
name: player,
|
||||||
"AccessControl",
|
}),
|
||||||
"[]",
|
targetPlayer: player,
|
||||||
undefined,
|
prefix: "AccessControl",
|
||||||
undefined,
|
brackets: "[]",
|
||||||
true,
|
utf8Support: true,
|
||||||
);
|
});
|
||||||
releaser.release();
|
releaser.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,34 +284,74 @@ function keyboardLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cliLoop() {
|
||||||
|
let printTargetPlayer: string | undefined;
|
||||||
|
const cli = createAccessControlCli({
|
||||||
|
configFilepath: configFilepath,
|
||||||
|
reloadConfig: () => reloadConfig(),
|
||||||
|
logger: logger,
|
||||||
|
print: (msg) =>
|
||||||
|
chatManager.sendMessage({
|
||||||
|
message: msg,
|
||||||
|
targetPlayer: printTargetPlayer,
|
||||||
|
prefix: "Access Control System",
|
||||||
|
brackets: "[]",
|
||||||
|
utf8Support: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const result = chatManager.getReceivedMessage();
|
||||||
|
if (result.isErr()) {
|
||||||
|
sleep(0.5);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
logger.debug(`Received message: ${result.value.message}`);
|
||||||
|
|
||||||
|
const ev = result.value;
|
||||||
|
|
||||||
|
let releaser = configLock.tryAcquireRead();
|
||||||
|
while (releaser === undefined) {
|
||||||
|
sleep(0.1);
|
||||||
|
releaser = configLock.tryAcquireRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = config.adminGroupConfig.groupUsers.includes(ev.username);
|
||||||
|
|
||||||
|
releaser.release();
|
||||||
|
if (!isAdmin) continue;
|
||||||
|
if (!ev.message.startsWith("@AC")) continue;
|
||||||
|
|
||||||
|
printTargetPlayer = ev.username;
|
||||||
|
logger.info(
|
||||||
|
`Received command "${ev.message}" from admin ${printTargetPlayer}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const commandArgs = ev.message
|
||||||
|
.substring(3)
|
||||||
|
.split(" ")
|
||||||
|
.filter((s) => s.length > 0);
|
||||||
|
logger.debug(`Command arguments: ${commandArgs.join(", ")}`);
|
||||||
|
|
||||||
|
cli(commandArgs);
|
||||||
|
printTargetPlayer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function main(args: string[]) {
|
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") {
|
||||||
// 创建CLI处理器
|
|
||||||
const cli = createAccessControlCLI({
|
|
||||||
configFilepath: configFilepath,
|
|
||||||
reloadConfig: () => reloadConfig(),
|
|
||||||
log: logger,
|
|
||||||
chatBox: chatBox,
|
|
||||||
});
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"Access Control System started. Press 'c' to open configuration TUI.",
|
"Access Control System started. Press 'c' to open configuration TUI.",
|
||||||
);
|
);
|
||||||
parallel.waitForAll(
|
parallel.waitForAll(
|
||||||
() => {
|
() => mainLoop(),
|
||||||
mainLoop();
|
() => gTimerManager.run(),
|
||||||
},
|
() => cliLoop(),
|
||||||
() => {
|
() => watchLoop(),
|
||||||
cli.startConfigLoop();
|
() => keyboardLoop(),
|
||||||
},
|
() => chatManager.run(),
|
||||||
() => {
|
|
||||||
watchLoop();
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
keyboardLoop();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ const chatToastCommand: Command<AppContext> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const toast: ChatToast = {
|
const toast: ChatToast = {
|
||||||
username: args.player as string,
|
targetPlayer: args.player as string,
|
||||||
title: args.title as string,
|
title: args.title as string,
|
||||||
message: args.message as string,
|
message: args.message as string,
|
||||||
prefix: options.prefix as string,
|
prefix: options.prefix as string,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Queue } from "./datatype/Queue";
|
import { Queue } from "./datatype/Queue";
|
||||||
import { ChatBoxEvent, pullEventAs } from "./event";
|
import { ChatBoxEvent, pullEventAs } from "./event";
|
||||||
import { Result, Ok, Err } from "./thirdparty/ts-result-es";
|
import { Result, Ok, Err } from "./thirdparty/ts-result-es";
|
||||||
|
import { gTimerManager } from "./TimerManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat manager error types
|
* Chat manager error types
|
||||||
@@ -50,7 +51,7 @@ interface ChatBasicMessage {
|
|||||||
*/
|
*/
|
||||||
export interface ChatToast extends ChatBasicMessage {
|
export interface ChatToast extends ChatBasicMessage {
|
||||||
/** Target player username to send the toast to */
|
/** Target player username to send the toast to */
|
||||||
username: string;
|
targetPlayer: string;
|
||||||
/** Title of the toast notification */
|
/** Title of the toast notification */
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
@@ -88,6 +89,9 @@ export class ChatManager {
|
|||||||
to control the running state of loops */
|
to control the running state of loops */
|
||||||
private isRunning = false;
|
private isRunning = false;
|
||||||
|
|
||||||
|
/** Lua thread for managing chat operations */
|
||||||
|
private thread?: LuaThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor - initializes the ChatManager with available ChatBox peripherals
|
* Constructor - initializes the ChatManager with available ChatBox peripherals
|
||||||
* @param peripherals Array of ChatBox peripherals to manage
|
* @param peripherals Array of ChatBox peripherals to manage
|
||||||
@@ -183,33 +187,18 @@ export class ChatManager {
|
|||||||
|
|
||||||
this.idleChatboxes[chatboxIndex] = false;
|
this.idleChatboxes[chatboxIndex] = false;
|
||||||
|
|
||||||
try {
|
if (!gTimerManager.status()) {
|
||||||
// Set timer to mark chatbox as idle after 1 second cooldown
|
|
||||||
const timerId = os.startTimer(1);
|
|
||||||
|
|
||||||
// Start a coroutine to wait for the timer and mark chatbox as idle
|
|
||||||
coroutine.resume(
|
|
||||||
coroutine.create(() => {
|
|
||||||
while (true) {
|
|
||||||
const [_eventName, id] = os.pullEvent("timer");
|
|
||||||
if (id === timerId) {
|
|
||||||
this.idleChatboxes[chatboxIndex] = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Ok(undefined);
|
|
||||||
} catch (error) {
|
|
||||||
// Revert chatbox state if timer setup fails
|
|
||||||
this.idleChatboxes[chatboxIndex] = true;
|
|
||||||
return new Err({
|
return new Err({
|
||||||
kind: "ChatManager",
|
kind: "ChatManager",
|
||||||
reason: `Failed to set chatbox timer: ${String(error)}`,
|
reason: "TimerManager is not running",
|
||||||
chatboxIndex,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gTimerManager.setTimeOut(1, () => {
|
||||||
|
this.idleChatboxes[chatboxIndex] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -328,7 +317,7 @@ export class ChatManager {
|
|||||||
[success, errorMsg] = chatbox.sendToastToPlayer(
|
[success, errorMsg] = chatbox.sendToastToPlayer(
|
||||||
toast.message,
|
toast.message,
|
||||||
toast.title,
|
toast.title,
|
||||||
toast.username,
|
toast.targetPlayer,
|
||||||
toast.prefix,
|
toast.prefix,
|
||||||
toast.brackets,
|
toast.brackets,
|
||||||
toast.bracketColor,
|
toast.bracketColor,
|
||||||
@@ -349,7 +338,7 @@ export class ChatManager {
|
|||||||
[success, errorMsg] = chatbox.sendFormattedToastToPlayer(
|
[success, errorMsg] = chatbox.sendFormattedToastToPlayer(
|
||||||
messageJson,
|
messageJson,
|
||||||
titleJson,
|
titleJson,
|
||||||
toast.username,
|
toast.targetPlayer,
|
||||||
toast.prefix,
|
toast.prefix,
|
||||||
toast.brackets,
|
toast.brackets,
|
||||||
toast.bracketColor,
|
toast.bracketColor,
|
||||||
@@ -489,7 +478,7 @@ export class ChatManager {
|
|||||||
* Useful when you need to run other code alongside the ChatManager
|
* Useful when you need to run other code alongside the ChatManager
|
||||||
* @returns Result indicating success or failure of async startup
|
* @returns Result indicating success or failure of async startup
|
||||||
*/
|
*/
|
||||||
public runAsync(): Result<void, ChatManagerError> {
|
public runAsync(): Result<LuaThread, ChatManagerError> {
|
||||||
if (this.isRunning) {
|
if (this.isRunning) {
|
||||||
return new Err({
|
return new Err({
|
||||||
kind: "ChatManager",
|
kind: "ChatManager",
|
||||||
@@ -499,18 +488,17 @@ export class ChatManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.isRunning = true;
|
this.isRunning = true;
|
||||||
|
this.thread = coroutine.create(() => {
|
||||||
// Start the run method in a separate coroutine
|
|
||||||
coroutine.resume(
|
|
||||||
coroutine.create(() => {
|
|
||||||
const result = this.run();
|
const result = this.run();
|
||||||
if (result.isErr()) {
|
if (result.isErr()) {
|
||||||
print(`ChatManager async error: ${result.error.reason}`);
|
print(`ChatManager async error: ${result.error.reason}`);
|
||||||
}
|
}
|
||||||
}),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return new Ok(undefined);
|
// Start the run method in a separate coroutine
|
||||||
|
coroutine.resume(this.thread);
|
||||||
|
|
||||||
|
return new Ok(this.thread);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.isRunning = false;
|
this.isRunning = false;
|
||||||
return new Err({
|
return new Err({
|
||||||
|
|||||||
36
src/lib/TimerManager.ts
Normal file
36
src/lib/TimerManager.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { pullEventAs, TimerEvent } from "./event";
|
||||||
|
import { Result, Ok, Err, Option, Some, None } from "./thirdparty/ts-result-es";
|
||||||
|
|
||||||
|
class TimerManager {
|
||||||
|
private isRunning = false;
|
||||||
|
|
||||||
|
private timerTaskMap = new Map<number, () => void>();
|
||||||
|
|
||||||
|
// Don't put heavy logic on callback function
|
||||||
|
public setTimeOut(delay: number, callback: () => void): void {
|
||||||
|
const timerId = os.startTimer(delay);
|
||||||
|
this.timerTaskMap.set(timerId, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public run() {
|
||||||
|
this.isRunning = true;
|
||||||
|
while (this.isRunning) {
|
||||||
|
const event = pullEventAs(TimerEvent, "timer");
|
||||||
|
if (event === undefined) continue;
|
||||||
|
|
||||||
|
const task = this.timerTaskMap.get(event.id);
|
||||||
|
if (task === undefined) continue;
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {
|
||||||
|
this.isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public status(): boolean {
|
||||||
|
return this.isRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const gTimerManager = new TimerManager();
|
||||||
Reference in New Issue
Block a user