mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-05 03:37:50 +08:00
Compare commits
2 Commits
7e03d960bd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4e74dcfa0 | ||
|
|
2f57d9ab3d |
12
README.md
12
README.md
@@ -30,7 +30,17 @@ A declarative, reactive TUI (Terminal User Interface) framework inspired by [Sol
|
|||||||
- **Control Flow:** Includes `<For>` and `<Show>` components for conditional and list-based rendering.
|
- **Control Flow:** Includes `<For>` and `<Show>` components for conditional and list-based rendering.
|
||||||
- **Component-Based:** Structure your UI into reusable components. See `src/tuiExample/main.ts` for a demo.
|
- **Component-Based:** Structure your UI into reusable components. See `src/tuiExample/main.ts` for a demo.
|
||||||
|
|
||||||
### 4. Core Libraries
|
### 4. ccCLI Framework
|
||||||
|
A lightweight, functional-style framework for building command-line interfaces (CLIs) within CC:Tweaked. It supports nested commands, arguments, options, and automatic help generation. See the [ccCLI Documentation](docs/ccCLI.md) for more details.
|
||||||
|
|
||||||
|
- **Declarative API:** Define commands, arguments, and options using a simple, object-based structure.
|
||||||
|
- **Nested Commands:** Organize complex applications with subcommands (e.g., `mycli command subcommand`).
|
||||||
|
- **Automatic Help:** Generates detailed help messages for commands and subcommands.
|
||||||
|
- **Global Context:** Inject shared state or services into command actions.
|
||||||
|
- **Type-Safe:** Built with TypeScript for robust development.
|
||||||
|
|
||||||
|
### 5. Core Libraries
|
||||||
|
- **`ChatManager`:** A powerful manager for `chatBox` peripherals that handles message queuing, cooldowns, and asynchronous sending/receiving. See the [ChatManager Documentation](docs/ChatManager.md) for more details.
|
||||||
- **`ccLog`:** A robust logging library with automatic, time-based log file rotation.
|
- **`ccLog`:** A robust logging library with automatic, time-based log file rotation.
|
||||||
- **`PeripheralManager`:** A utility for easily finding and requiring peripherals by name or type.
|
- **`PeripheralManager`:** A utility for easily finding and requiring peripherals by name or type.
|
||||||
- **`CraftManager`:** A library for parsing and executing crafting recipes from Create mod packages.
|
- **`CraftManager`:** A library for parsing and executing crafting recipes from Create mod packages.
|
||||||
|
|||||||
235
docs/ChatManager.md
Normal file
235
docs/ChatManager.md
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
# ChatManager Documentation
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
`ChatManager` is a powerful utility for managing interactions with one or more `chatBox` peripherals in ComputerCraft. It simplifies the process of sending and receiving chat messages by handling complexities like peripheral cooldowns, message queuing, and asynchronous operations.
|
||||||
|
|
||||||
|
It is designed for applications that need to reliably send a high volume of messages or toasts without getting bogged down by peripheral limitations, or for applications that need to listen for commands or messages from players.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **Multi-Peripheral Management:** Seamlessly manages one or more `chatBox` peripherals.
|
||||||
|
* **Message Queuing:** Automatically queues messages and toasts, sending them as chatboxes become available.
|
||||||
|
* **Cooldown Handling:** Respects the 1-second cooldown of chatboxes to prevent message loss.
|
||||||
|
* **Asynchronous Operation:** Can run in the background (`runAsync`) without blocking your main program loop.
|
||||||
|
* **Message Buffering:** Receives and buffers incoming chat messages for your application to process.
|
||||||
|
* **Queued and Immediate Sending:** Supports both adding messages to a queue and sending them immediately (if a chatbox is available).
|
||||||
|
* **Rich Content Support:** Send simple strings or complex formatted messages using `MinecraftTextComponent`.
|
||||||
|
* **Robust Error Handling:** Uses a `Result`-based API to make error handling explicit and reliable.
|
||||||
|
* **Comprehensive API:** Provides methods for sending global messages, private messages, and toast notifications.
|
||||||
|
|
||||||
|
## Tutorial: Getting Started with ChatManager
|
||||||
|
|
||||||
|
Here’s how to integrate `ChatManager` into your project.
|
||||||
|
|
||||||
|
### 1. Initialization
|
||||||
|
|
||||||
|
First, find your available `chatBox` peripherals and create a `ChatManager` instance.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ChatManager } from '@/lib/ChatManager';
|
||||||
|
|
||||||
|
// Find all available chatbox peripherals
|
||||||
|
const peripheralNames = peripheral.getNames();
|
||||||
|
const chatboxPeripherals: ChatBoxPeripheral[] = [];
|
||||||
|
|
||||||
|
for (const name of peripheralNames) {
|
||||||
|
const peripheralType = peripheral.getType(name);
|
||||||
|
if (peripheralType[0] === "chatBox") {
|
||||||
|
const chatbox = peripheral.wrap(name) as ChatBoxPeripheral;
|
||||||
|
chatboxPeripherals.push(chatbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chatboxPeripherals.length === 0) {
|
||||||
|
print("Error: No chatbox peripherals found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the manager instance
|
||||||
|
const chatManager = new ChatManager(chatboxPeripherals);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Running the Manager
|
||||||
|
|
||||||
|
To start the sending and receiving loops, you must run the manager. For most use cases, running it asynchronously is best.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Start ChatManager in the background so it doesn't block the main program
|
||||||
|
const runResult = chatManager.runAsync();
|
||||||
|
|
||||||
|
if (runResult.isErr()) {
|
||||||
|
print(`Warning: Failed to start ChatManager: ${runResult.error.reason}`);
|
||||||
|
} else {
|
||||||
|
print("ChatManager started successfully!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Your main program logic can continue here...
|
||||||
|
```
|
||||||
|
**Important:** `ChatManager` relies on `gTimerManager` to handle cooldowns. Ensure you are also running the global timer manager in your application.
|
||||||
|
```typescript
|
||||||
|
import { gTimerManager } from "@/lib/TimerManager";
|
||||||
|
|
||||||
|
// In your main parallel loop
|
||||||
|
parallel.waitForAll(
|
||||||
|
() => yourMainLoop(),
|
||||||
|
() => chatManager.run(), // if you choose the blocking run
|
||||||
|
() => gTimerManager.run()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Sending a Message (Queued)
|
||||||
|
|
||||||
|
Use `sendMessage` to add a message to the queue. `ChatManager` will send it as soon as a chatbox is free.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Send a global message
|
||||||
|
chatManager.sendMessage({
|
||||||
|
message: "Hello, world!",
|
||||||
|
prefix: "MySystem",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send a private message
|
||||||
|
chatManager.sendMessage({
|
||||||
|
message: "This is a secret.",
|
||||||
|
targetPlayer: "Steve",
|
||||||
|
prefix: "Whisper",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Sending a Toast (Queued)
|
||||||
|
|
||||||
|
Similarly, use `sendToast` to queue a toast notification.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
chatManager.sendToast({
|
||||||
|
title: "Server Alert",
|
||||||
|
message: "Restart in 5 minutes!",
|
||||||
|
targetPlayer: "Steve",
|
||||||
|
prefix: "Admin",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Receiving Messages
|
||||||
|
|
||||||
|
Use `getReceivedMessage` to pull incoming chat events from the buffer. It's best to do this in a loop.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function myCliLoop() {
|
||||||
|
while (true) {
|
||||||
|
const result = chatManager.getReceivedMessage();
|
||||||
|
if (result.isOk()) {
|
||||||
|
const event = result.value;
|
||||||
|
print(`[${event.username}]: ${event.message}`);
|
||||||
|
// Process the command or message...
|
||||||
|
} else {
|
||||||
|
// Buffer is empty, wait a bit before checking again
|
||||||
|
sleep(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Topics
|
||||||
|
|
||||||
|
### Immediate Sending
|
||||||
|
|
||||||
|
If you need to send a message right away and bypass the queue, use the `Immediate` methods. These will fail if no chatbox is currently available.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const result = chatManager.sendMessageImmediate({
|
||||||
|
message: "URGENT!",
|
||||||
|
targetPlayer: "Admin",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.isErr()) {
|
||||||
|
if (result.error.kind === "NoIdleChatbox") {
|
||||||
|
print("Could not send immediately: all chatboxes are busy.");
|
||||||
|
} else {
|
||||||
|
print(`Failed to send message: ${result.error.reason}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rich Text Messages (`MinecraftTextComponent`)
|
||||||
|
|
||||||
|
You can send fully formatted messages by providing a `MinecraftTextComponent` object instead of a string.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const richMessage: MinecraftTextComponent = {
|
||||||
|
text: "This is ",
|
||||||
|
color: "gold",
|
||||||
|
extra: [
|
||||||
|
{ text: "important!", color: "red", bold: true }
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
chatManager.sendMessage({
|
||||||
|
message: richMessage,
|
||||||
|
targetPlayer: "AllPlayers",
|
||||||
|
utf8Support: true, // Recommended for complex components
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
Methods return a `Result` object (`Ok` or `Err`). Always check the result to handle potential failures gracefully.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const result = chatManager.sendMessage(message);
|
||||||
|
if (result.isErr()) {
|
||||||
|
logger.error(`Failed to queue message: ${result.error.reason}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The possible error `kind`s are:
|
||||||
|
* `ChatManagerError`: General errors, e.g., failure to enqueue.
|
||||||
|
* `NoIdleChatboxError`: Returned by `Immediate` methods when no chatbox is free.
|
||||||
|
* `SendFailureError`: A hardware or permission error occurred during sending.
|
||||||
|
* `EmptyBufferError`: Returned by `getReceivedMessage` when the buffer is empty.
|
||||||
|
|
||||||
|
### Status and Management
|
||||||
|
|
||||||
|
You can inspect and control the `ChatManager` at runtime.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Get the number of items waiting to be sent
|
||||||
|
const pending = chatManager.getPendingMessageCount();
|
||||||
|
print(`Messages in queue: ${pending}`);
|
||||||
|
|
||||||
|
// Get the number of received messages waiting to be processed
|
||||||
|
const buffered = chatManager.getBufferedMessageCount();
|
||||||
|
print(`Received messages in buffer: ${buffered}`);
|
||||||
|
|
||||||
|
// Get the status of each chatbox (true = idle, false = busy)
|
||||||
|
const statuses = chatManager.getChatboxStatus();
|
||||||
|
|
||||||
|
// Clear the sending queues
|
||||||
|
chatManager.clearQueues();
|
||||||
|
|
||||||
|
// Clear the received message buffer
|
||||||
|
chatManager.clearBuffer();
|
||||||
|
|
||||||
|
// Stop the manager's background loops
|
||||||
|
chatManager.stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Core Class
|
||||||
|
* `ChatManager(peripherals: ChatBoxPeripheral[])`
|
||||||
|
|
||||||
|
### Primary Methods
|
||||||
|
* `run(): Result<void, ChatManagerError>`: Starts the manager (blocking).
|
||||||
|
* `runAsync(): Result<LuaThread, ChatManagerError>`: Starts the manager in the background.
|
||||||
|
* `stop(): Result<void, ChatManagerError>`: Stops the background loops.
|
||||||
|
* `sendMessage(message: ChatMessage): Result<void, ChatManagerError>`: Queues a chat message.
|
||||||
|
* `sendToast(toast: ChatToast): Result<void, ChatManagerError>`: Queues a toast.
|
||||||
|
* `getReceivedMessage(): Result<ChatBoxEvent, EmptyBufferError>`: Retrieves a message from the receive buffer.
|
||||||
|
* `sendMessageImmediate(message: ChatMessage): Result<void, ChatError>`: Sends a message immediately.
|
||||||
|
* `sendToastImmediate(toast: ChatToast): Result<void, ChatError>`: Sends a toast immediately.
|
||||||
|
|
||||||
|
### Interfaces
|
||||||
|
* `ChatMessage`
|
||||||
|
* `ChatToast`
|
||||||
|
* `ChatError` (union of all possible error types)
|
||||||
248
docs/ccCLI.md
Normal file
248
docs/ccCLI.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# ccCLI Framework Documentation
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
`ccCLI` is a lightweight, functional-style framework for building command-line interfaces (CLIs) within the CC:Tweaked environment using TSTL (TypeScriptToLua). It provides a declarative and type-safe way to define commands, arguments, and options, with built-in support for nested commands, automatic help generation, and robust error handling.
|
||||||
|
|
||||||
|
Its design is inspired by modern CLI libraries and emphasizes simplicity and ease of use, allowing developers to quickly structure complex command-based applications.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* **Declarative API:** Define commands as simple objects.
|
||||||
|
* **Type-Safe:** Leverage TypeScript for defining commands, arguments, options, and context.
|
||||||
|
* **Nested Commands:** Easily create command groups and subcommands (e.g., `git remote add`).
|
||||||
|
* **Automatic Help Generation:** Generates `--help` messages for the root command and all subcommands.
|
||||||
|
* **Flexible Argument & Option Parsing:** Supports long names (`--verbose`), short names (`-v`), value assignment (`--file=path.txt`), and boolean flags.
|
||||||
|
* **Global Context Injection:** Share state, services, or configuration across all commands.
|
||||||
|
* **Result-Based Error Handling:** Command actions return a `Result` type, ensuring that errors are handled explicitly.
|
||||||
|
* **No Dependencies:** Written in pure TypeScript with no external runtime dependencies.
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
The framework is built around a few key interfaces:
|
||||||
|
|
||||||
|
* `Command<TContext>`: The central piece. It defines a command's name, description, arguments, options, subcommands, and the action to perform.
|
||||||
|
* `Argument`: Defines a positional argument for a command. It can be marked as required.
|
||||||
|
* `Option`: Defines a named option (flag). It can have a long name, a short name, a default value, and be marked as required.
|
||||||
|
* `ActionContext<TContext>`: The object passed to every command's `action` function. It contains the parsed `args`, `options`, and the shared `context` object.
|
||||||
|
|
||||||
|
## Tutorial: Creating a Simple Calculator CLI
|
||||||
|
|
||||||
|
Let's build a simple calculator to see how `ccCLI` works.
|
||||||
|
|
||||||
|
### 1. Define the Global Context (Optional)
|
||||||
|
|
||||||
|
The global context is a powerful feature for sharing data or services. Let's define a context for our app.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/cliExample/main.ts
|
||||||
|
|
||||||
|
interface AppContext {
|
||||||
|
appName: string;
|
||||||
|
log: (message: string) => void;
|
||||||
|
debugMode: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Define Commands
|
||||||
|
|
||||||
|
Commands are just JavaScript objects. The logic goes into the `action` function.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/cliExample/main.ts
|
||||||
|
|
||||||
|
import { Command, CliError } from "../lib/ccCLI/index";
|
||||||
|
import { Ok, Result } from "../lib/thirdparty/ts-result-es";
|
||||||
|
|
||||||
|
const addCommand: Command<AppContext> = {
|
||||||
|
name: "add",
|
||||||
|
description: "Adds two numbers together",
|
||||||
|
args: [
|
||||||
|
{ name: "a", description: "The first number", required: true },
|
||||||
|
{ name: "b", description: "The second number", required: true },
|
||||||
|
],
|
||||||
|
action: ({ args, context }): Result<void, CliError> => {
|
||||||
|
context.log(`Executing 'add' command in '${context.appName}'`);
|
||||||
|
|
||||||
|
const a = tonumber(args.a as string);
|
||||||
|
const b = tonumber(args.b as string);
|
||||||
|
|
||||||
|
if (a === undefined || b === undefined) {
|
||||||
|
print("Error: Arguments must be numbers.");
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = a + b;
|
||||||
|
print(`${a} + ${b} = ${result}`);
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create Nested Commands
|
||||||
|
|
||||||
|
You can group commands under a parent command using the `subcommands` property.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/cliExample/main.ts
|
||||||
|
|
||||||
|
// (addCommand is defined above, subtractCommand would be similar)
|
||||||
|
|
||||||
|
const mathCommand: Command<AppContext> = {
|
||||||
|
name: "math",
|
||||||
|
description: "Mathematical operations",
|
||||||
|
subcommands: new Map([
|
||||||
|
["add", addCommand],
|
||||||
|
["subtract", subtractCommand], // Assuming subtractCommand is defined
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
If a command with subcommands is called without an action, it will automatically display its help page.
|
||||||
|
|
||||||
|
### 4. Define the Root Command
|
||||||
|
|
||||||
|
The root command is the entry point for your entire application. It contains all top-level commands and global options.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/cliExample/main.ts
|
||||||
|
|
||||||
|
const rootCommand: Command<AppContext> = {
|
||||||
|
name: "calculator",
|
||||||
|
description: "A feature-rich calculator program",
|
||||||
|
options: new Map([
|
||||||
|
[
|
||||||
|
"debug",
|
||||||
|
{
|
||||||
|
name: "debug",
|
||||||
|
shortName: "d",
|
||||||
|
description: "Enable debug mode",
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
subcommands: new Map([
|
||||||
|
["math", mathCommand],
|
||||||
|
// other commands...
|
||||||
|
]),
|
||||||
|
action: ({ context }) => {
|
||||||
|
print(`Welcome to ${context.appName}!`);
|
||||||
|
print("Use --help to see available commands");
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Create and Run the CLI
|
||||||
|
|
||||||
|
Finally, create the context instance and pass it along with the root command to `createCli`. This returns a handler function that you can call with the program's arguments.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/cliExample/main.ts
|
||||||
|
|
||||||
|
import { createCli } from "../lib/ccCLI/index";
|
||||||
|
|
||||||
|
// Create global context instance
|
||||||
|
const appContext: AppContext = {
|
||||||
|
appName: "MyAwesome Calculator",
|
||||||
|
debugMode: false,
|
||||||
|
log: (message) => {
|
||||||
|
if (appContext.debugMode) {
|
||||||
|
print(`[LOG] ${message}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the CLI handler
|
||||||
|
const cli = createCli(rootCommand, { globalContext: appContext });
|
||||||
|
|
||||||
|
// Get arguments and run
|
||||||
|
const args = [...$vararg];
|
||||||
|
cli(args);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
You can now run your CLI from the ComputerCraft terminal:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
> lua program.lua math add 5 7
|
||||||
|
12
|
||||||
|
|
||||||
|
> lua program.lua --debug math add 5 7
|
||||||
|
[LOG] Executing 'add' command in 'MyAwesome Calculator'
|
||||||
|
12
|
||||||
|
|
||||||
|
> lua program.lua math --help
|
||||||
|
# Displays help for the 'math' command
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Topics
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
Arguments are positional values passed after a command. They are defined in an array.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
args: [
|
||||||
|
{ name: "a", description: "The first number", required: true },
|
||||||
|
{ name: "b", description: "The second number" }, // optional
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
Options are named values (flags) that can appear anywhere. They are defined in a `Map`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
options: new Map([
|
||||||
|
[
|
||||||
|
"name", // The key in the map must match the option's name
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
shortName: "n",
|
||||||
|
description: "The name to greet",
|
||||||
|
defaultValue: "World",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"force",
|
||||||
|
{
|
||||||
|
name: "force",
|
||||||
|
description: "Force the operation",
|
||||||
|
defaultValue: false, // For boolean flags
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
```
|
||||||
|
|
||||||
|
They can be used like this:
|
||||||
|
* `--name "John"` or `-n "John"`
|
||||||
|
* `--name="John"`
|
||||||
|
* `--force` (sets the value to `true`)
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
The `action` function must return a `Result<void, CliError>`.
|
||||||
|
* Return `Ok.EMPTY` on success.
|
||||||
|
* The framework automatically handles parsing errors like missing arguments or unknown commands. You can return your own errors from within an action if needed, though this is less common. The primary mechanism is simply printing an error message and returning `Ok.EMPTY`.
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
The public API is exposed through `src/lib/ccCLI/index.ts`.
|
||||||
|
|
||||||
|
### Core Function
|
||||||
|
|
||||||
|
* `createCli<TContext>(rootCommand, options)`: Creates the main CLI handler function.
|
||||||
|
* `rootCommand`: The top-level command of your application.
|
||||||
|
* `options.globalContext`: The context object to be injected into all actions.
|
||||||
|
* `options.writer`: An optional function to handle output (defaults to `textutils.pagedPrint`).
|
||||||
|
|
||||||
|
### Core Types
|
||||||
|
|
||||||
|
* `Command<TContext>`
|
||||||
|
* `Argument`
|
||||||
|
* `Option`
|
||||||
|
* `ActionContext<TContext>`
|
||||||
|
* `CliError`
|
||||||
|
|
||||||
|
This documentation provides a comprehensive overview of the `ccCLI` framework. By following the tutorial and referencing the examples, you can build powerful and well-structured command-line tools for CC:Tweaked.
|
||||||
@@ -117,8 +117,8 @@ const delCommand: Command<AppContext> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const listCommand: Command<AppContext> = {
|
const listUserCommand: Command<AppContext> = {
|
||||||
name: "list",
|
name: "user",
|
||||||
description: "列出所有玩家及其所在的用户组",
|
description: "列出所有玩家及其所在的用户组",
|
||||||
action: ({ context }) => {
|
action: ({ context }) => {
|
||||||
const config = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
@@ -134,213 +134,227 @@ const listCommand: Command<AppContext> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCommand: Command<AppContext> = {
|
const listGroupCommand: Command<AppContext> = {
|
||||||
name: "set",
|
name: "group",
|
||||||
|
description: "显示详细的用户组配置信息",
|
||||||
|
action: ({ context }) => {
|
||||||
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
let groupsMessage = `管理员组: ${config.adminGroupConfig.groupName}\n`;
|
||||||
|
groupsMessage += ` 用户: [${config.adminGroupConfig.groupUsers.join(
|
||||||
|
", ",
|
||||||
|
)}]\n`;
|
||||||
|
groupsMessage += ` 允许: ${config.adminGroupConfig.isAllowed}\n`;
|
||||||
|
groupsMessage += ` 通知: ${config.adminGroupConfig.isNotice}\n\n`;
|
||||||
|
|
||||||
|
for (const group of config.usersGroups) {
|
||||||
|
groupsMessage += `用户组: ${group.groupName}\n`;
|
||||||
|
groupsMessage += ` 用户: [${(group.groupUsers ?? []).join(", ")}]\n`;
|
||||||
|
groupsMessage += ` 允许: ${group.isAllowed}\n`;
|
||||||
|
groupsMessage += ` 通知: ${group.isNotice}\n`;
|
||||||
|
groupsMessage += "\n";
|
||||||
|
}
|
||||||
|
context.print({ text: groupsMessage.trim() });
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const listToastCommand: Command<AppContext> = {
|
||||||
|
name: "toast",
|
||||||
|
description: "显示 Toast 配置信息",
|
||||||
|
action: ({ context }) => {
|
||||||
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
let toastMessage = "默认 Toast 配置:\n";
|
||||||
|
toastMessage += ` 标题: ${config.welcomeToastConfig.title.text}\n`;
|
||||||
|
toastMessage += ` 消息: ${config.welcomeToastConfig.msg.text}\n`;
|
||||||
|
toastMessage += ` 前缀: ${config.welcomeToastConfig.prefix ?? "none"}\n`;
|
||||||
|
toastMessage += ` 括号: ${config.welcomeToastConfig.brackets ?? "none"}\n`;
|
||||||
|
toastMessage += ` 括号颜色: ${
|
||||||
|
config.welcomeToastConfig.bracketColor ?? "none"
|
||||||
|
}\n\n`;
|
||||||
|
|
||||||
|
toastMessage += "警告 Toast 配置:\n";
|
||||||
|
toastMessage += ` 标题: ${config.warnToastConfig.title.text}\n`;
|
||||||
|
toastMessage += ` 消息: ${config.warnToastConfig.msg.text}\n`;
|
||||||
|
toastMessage += ` 前缀: ${config.warnToastConfig.prefix ?? "none"}\n`;
|
||||||
|
toastMessage += ` 括号: ${config.warnToastConfig.brackets ?? "none"}\n`;
|
||||||
|
toastMessage += ` 括号颜色: ${
|
||||||
|
config.warnToastConfig.bracketColor ?? "none"
|
||||||
|
}`;
|
||||||
|
context.print({ text: toastMessage });
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const listAllCommand: Command<AppContext> = {
|
||||||
|
name: "all",
|
||||||
|
description: "显示基本配置信息概览",
|
||||||
|
action: ({ context }) => {
|
||||||
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
let allMessage = `检测范围: ${config.detectRange}\n`;
|
||||||
|
allMessage += `检测间隔: ${config.detectInterval}\n`;
|
||||||
|
allMessage += `警告间隔: ${config.watchInterval}\n`;
|
||||||
|
allMessage += `通知次数: ${config.noticeTimes}\n`;
|
||||||
|
allMessage += `全局欢迎功能: ${config.isWelcome}\n`;
|
||||||
|
allMessage += `全局警告功能: ${config.isWarn}\n\n`;
|
||||||
|
allMessage += "使用 'list group' 或 'list toast' 查看详细信息";
|
||||||
|
context.print({ text: allMessage });
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const listCommand: Command<AppContext> = {
|
||||||
|
name: "list",
|
||||||
|
description: "列出玩家、组信息或配置",
|
||||||
|
subcommands: new Map([
|
||||||
|
["user", listUserCommand],
|
||||||
|
["group", listGroupCommand],
|
||||||
|
["toast", listToastCommand],
|
||||||
|
["all", listAllCommand],
|
||||||
|
]),
|
||||||
|
action: ({ context }) => {
|
||||||
|
const config = loadConfig(context.configFilepath)!;
|
||||||
|
let allMessage = `检测范围: ${config.detectRange}\n`;
|
||||||
|
allMessage += `检测间隔: ${config.detectInterval}\n`;
|
||||||
|
allMessage += `警告间隔: ${config.watchInterval}\n`;
|
||||||
|
allMessage += `通知次数: ${config.noticeTimes}\n`;
|
||||||
|
allMessage += `全局欢迎功能: ${config.isWelcome}\n`;
|
||||||
|
allMessage += `全局警告功能: ${config.isWarn}\n\n`;
|
||||||
|
allMessage += "使用 'list group' 或 'list toast' 查看详细信息";
|
||||||
|
context.print({ text: allMessage });
|
||||||
|
return Ok.EMPTY;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const configCommand: Command<AppContext> = {
|
||||||
|
name: "config",
|
||||||
description: "配置访问控制设置",
|
description: "配置访问控制设置",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "option",
|
name: "option",
|
||||||
description: "要设置的选项 (warnInterval, detectInterval, detectRange)",
|
description:
|
||||||
|
"要设置的选项 (warnInterval, detectInterval, detectRange, noticeTimes, isWelcome, isWarn) 或用户组属性 (<groupName>.isAllowed, <groupName>.isNotice, <groupName>.isWelcome)",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{ name: "value", description: "要设置的值", 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);
|
|
||||||
|
|
||||||
if (isNaN(value)) {
|
|
||||||
context.print({ text: `无效的值: ${valueStr}. 必须是一个数字。` });
|
|
||||||
return Ok.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = loadConfig(context.configFilepath)!;
|
const config = loadConfig(context.configFilepath)!;
|
||||||
let message = "";
|
|
||||||
|
|
||||||
switch (option) {
|
// Check if it's a group property (contains a dot)
|
||||||
case "warnInterval":
|
if (option.includes(".")) {
|
||||||
config.watchInterval = value;
|
const dotIndex = option.indexOf(".");
|
||||||
message = `已设置警告间隔为 ${value}`;
|
const groupName = option.substring(0, dotIndex);
|
||||||
break;
|
const property = option.substring(dotIndex + 1);
|
||||||
case "detectInterval":
|
|
||||||
config.detectInterval = value;
|
let groupConfig: UserGroupConfig | undefined;
|
||||||
message = `已设置检测间隔为 ${value}`;
|
if (groupName === "admin") {
|
||||||
break;
|
groupConfig = config.adminGroupConfig;
|
||||||
case "detectRange":
|
} else {
|
||||||
config.detectRange = value;
|
groupConfig = config.usersGroups.find((g) => g.groupName === groupName);
|
||||||
message = `已设置检测范围为 ${value}`;
|
}
|
||||||
break;
|
|
||||||
default:
|
if (!groupConfig) {
|
||||||
|
context.print({ text: `用户组 ${groupName} 未找到` });
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boolValue = parseBoolean(valueStr);
|
||||||
|
if (boolValue === undefined) {
|
||||||
context.print({
|
context.print({
|
||||||
text: `未知选项: ${option}. 可用选项: warnInterval, detectInterval, detectRange`,
|
text: `无效的布尔值: ${valueStr}. 请使用 'true' 或 'false'.`,
|
||||||
});
|
});
|
||||||
return Ok.EMPTY;
|
return Ok.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveConfig(config, context.configFilepath);
|
let message = "";
|
||||||
context.reloadConfig();
|
switch (property) {
|
||||||
context.print({ text: message });
|
case "isAllowed":
|
||||||
return Ok.EMPTY;
|
groupConfig.isAllowed = boolValue;
|
||||||
},
|
message = `已设置 ${groupName}.isAllowed 为 ${boolValue}`;
|
||||||
};
|
break;
|
||||||
|
case "isNotice":
|
||||||
|
groupConfig.isNotice = boolValue;
|
||||||
|
message = `已设置 ${groupName}.isNotice 为 ${boolValue}`;
|
||||||
|
break;
|
||||||
|
case "isWelcome":
|
||||||
|
groupConfig.isWelcome = boolValue;
|
||||||
|
message = `已设置 ${groupName}.isWelcome 为 ${boolValue}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
context.print({
|
||||||
|
text: `未知属性: ${property}. 可用属性: isAllowed, isNotice, isWelcome`,
|
||||||
|
});
|
||||||
|
return Ok.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
const editGroupCommand: Command<AppContext> = {
|
saveConfig(config, context.configFilepath);
|
||||||
name: "group",
|
context.reloadConfig();
|
||||||
description: "编辑用户组属性",
|
context.print({ text: message });
|
||||||
args: [
|
return Ok.EMPTY;
|
||||||
{
|
|
||||||
name: "groupName",
|
|
||||||
description: "要编辑的用户组名称",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "property",
|
|
||||||
description: "要更改的属性 (isAllowed, isNotice)",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{ name: "value", description: "新值 (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)!;
|
|
||||||
|
|
||||||
let groupConfig: UserGroupConfig | undefined;
|
|
||||||
if (groupName === "admin") {
|
|
||||||
groupConfig = config.adminGroupConfig;
|
|
||||||
} else {
|
} else {
|
||||||
groupConfig = config.usersGroups.find((g) => g.groupName === groupName);
|
// Handle basic configuration options
|
||||||
}
|
let message = "";
|
||||||
|
|
||||||
if (!groupConfig) {
|
// Check if it's a boolean option
|
||||||
context.print({ text: `用户组 ${groupName} 未找到` });
|
if (option === "isWelcome" || option === "isWarn") {
|
||||||
return Ok.EMPTY;
|
const boolValue = parseBoolean(valueStr);
|
||||||
}
|
if (boolValue === undefined) {
|
||||||
|
context.print({
|
||||||
const boolValue = parseBoolean(valueStr);
|
text: `无效的布尔值: ${valueStr}. 请使用 'true' 或 'false'.`,
|
||||||
if (boolValue === undefined) {
|
});
|
||||||
context.print({
|
return Ok.EMPTY;
|
||||||
text: `无效的布尔值: ${valueStr}. 请使用 'true' 或 'false'.`,
|
}
|
||||||
});
|
|
||||||
return Ok.EMPTY;
|
switch (option) {
|
||||||
}
|
case "isWelcome":
|
||||||
|
config.isWelcome = boolValue;
|
||||||
let message = "";
|
message = `已设置全局欢迎功能为 ${boolValue}`;
|
||||||
switch (property) {
|
break;
|
||||||
case "isAllowed":
|
case "isWarn":
|
||||||
groupConfig.isAllowed = boolValue;
|
config.isWarn = boolValue;
|
||||||
message = `已设置 ${groupName}.isAllowed 为 ${boolValue}`;
|
message = `已设置全局警告功能为 ${boolValue}`;
|
||||||
break;
|
break;
|
||||||
case "isNotice":
|
}
|
||||||
groupConfig.isNotice = boolValue;
|
} else {
|
||||||
message = `已设置 ${groupName}.isNotice 为 ${boolValue}`;
|
// Handle numeric options
|
||||||
break;
|
const value = parseInt(valueStr);
|
||||||
default:
|
|
||||||
context.print({
|
if (isNaN(value)) {
|
||||||
text: `未知属性: ${property}. 可用属性: isAllowed, isNotice`,
|
context.print({ text: `无效的值: ${valueStr}. 必须是一个数字。` });
|
||||||
});
|
return Ok.EMPTY;
|
||||||
return Ok.EMPTY;
|
}
|
||||||
}
|
|
||||||
|
switch (option) {
|
||||||
saveConfig(config, context.configFilepath);
|
case "warnInterval":
|
||||||
context.reloadConfig();
|
config.watchInterval = value;
|
||||||
context.print({ text: message });
|
message = `已设置警告间隔为 ${value}`;
|
||||||
return Ok.EMPTY;
|
break;
|
||||||
},
|
case "detectInterval":
|
||||||
};
|
config.detectInterval = value;
|
||||||
|
message = `已设置检测间隔为 ${value}`;
|
||||||
const editCommand: Command<AppContext> = {
|
break;
|
||||||
name: "edit",
|
case "detectRange":
|
||||||
description: "编辑各项配置",
|
config.detectRange = value;
|
||||||
subcommands: new Map([["group", editGroupCommand]]),
|
message = `已设置检测范围为 ${value}`;
|
||||||
};
|
break;
|
||||||
|
case "noticeTimes":
|
||||||
const showConfigCommand: Command<AppContext> = {
|
config.noticeTimes = value;
|
||||||
name: "showconfig",
|
message = `已设置通知次数为 ${value}`;
|
||||||
description: "显示配置",
|
break;
|
||||||
options: new Map([
|
default:
|
||||||
[
|
context.print({
|
||||||
"type",
|
text: `未知选项: ${option}. 可用选项: warnInterval, detectInterval, detectRange, noticeTimes, isWelcome, isWarn 或 <groupName>.isAllowed, <groupName>.isNotice, <groupName>.isWelcome`,
|
||||||
{
|
});
|
||||||
name: "type",
|
return Ok.EMPTY;
|
||||||
description: "要显示的配置类型 (groups, toast, all)",
|
|
||||||
required: false,
|
|
||||||
defaultValue: "all",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
action: ({ options, context }) => {
|
|
||||||
const type = options.type as string;
|
|
||||||
const config = loadConfig(context.configFilepath)!;
|
|
||||||
let message = "";
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "groups": {
|
|
||||||
let groupsMessage = `管理员组: ${config.adminGroupConfig.groupName}\n`;
|
|
||||||
groupsMessage += ` 用户: [${config.adminGroupConfig.groupUsers.join(
|
|
||||||
", ",
|
|
||||||
)}]\n`;
|
|
||||||
groupsMessage += ` 允许: ${config.adminGroupConfig.isAllowed}\n`;
|
|
||||||
groupsMessage += ` 通知: ${config.adminGroupConfig.isNotice}\n\n`;
|
|
||||||
|
|
||||||
for (const group of config.usersGroups) {
|
|
||||||
groupsMessage += `用户组: ${group.groupName}\n`;
|
|
||||||
groupsMessage += ` 用户: [${(group.groupUsers ?? []).join(", ")}]\n`;
|
|
||||||
groupsMessage += ` 允许: ${group.isAllowed}\n`;
|
|
||||||
groupsMessage += ` 通知: ${group.isNotice}\n`;
|
|
||||||
groupsMessage += "\n";
|
|
||||||
}
|
}
|
||||||
message = groupsMessage.trim();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "toast": {
|
saveConfig(config, context.configFilepath);
|
||||||
let toastMessage = "默认 Toast 配置:\n";
|
context.reloadConfig();
|
||||||
toastMessage += ` 标题: ${config.welcomeToastConfig.title.text}\n`;
|
context.print({ text: message });
|
||||||
toastMessage += ` 消息: ${config.welcomeToastConfig.msg.text}\n`;
|
return Ok.EMPTY;
|
||||||
toastMessage += ` 前缀: ${
|
|
||||||
config.welcomeToastConfig.prefix ?? "none"
|
|
||||||
}\n`;
|
|
||||||
toastMessage += ` 括号: ${
|
|
||||||
config.welcomeToastConfig.brackets ?? "none"
|
|
||||||
}\n`;
|
|
||||||
toastMessage += ` 括号颜色: ${
|
|
||||||
config.welcomeToastConfig.bracketColor ?? "none"
|
|
||||||
}\n\n`;
|
|
||||||
|
|
||||||
toastMessage += "警告 Toast 配置:\n";
|
|
||||||
toastMessage += ` 标题: ${config.warnToastConfig.title.text}\n`;
|
|
||||||
toastMessage += ` 消息: ${config.warnToastConfig.msg.text}\n`;
|
|
||||||
toastMessage += ` 前缀: ${config.warnToastConfig.prefix ?? "none"}\n`;
|
|
||||||
toastMessage += ` 括号: ${
|
|
||||||
config.warnToastConfig.brackets ?? "none"
|
|
||||||
}\n`;
|
|
||||||
toastMessage += ` 括号颜色: ${
|
|
||||||
config.warnToastConfig.bracketColor ?? "none"
|
|
||||||
}`;
|
|
||||||
message = toastMessage;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "all": {
|
|
||||||
let allMessage = `检测范围: ${config.detectRange}\n`;
|
|
||||||
allMessage += `检测间隔: ${config.detectInterval}\n`;
|
|
||||||
allMessage += `警告间隔: ${config.watchInterval}\n\n`;
|
|
||||||
allMessage +=
|
|
||||||
"使用 'showconfig --type groups' 或 'showconfig --type toast' 查看详细信息";
|
|
||||||
message = allMessage;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
message = `无效类型: ${type}. 可用类型: groups, toast, all`;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
context.print({ text: message });
|
|
||||||
return Ok.EMPTY;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -352,9 +366,7 @@ const rootCommand: Command<AppContext> = {
|
|||||||
["add", addCommand],
|
["add", addCommand],
|
||||||
["del", delCommand],
|
["del", delCommand],
|
||||||
["list", listCommand],
|
["list", listCommand],
|
||||||
["set", setCommand],
|
["config", configCommand],
|
||||||
["edit", editCommand],
|
|
||||||
["showconfig", showConfigCommand],
|
|
||||||
]),
|
]),
|
||||||
action: ({ context }) => {
|
action: ({ context }) => {
|
||||||
context.print([
|
context.print([
|
||||||
@@ -383,6 +395,6 @@ const rootCommand: Command<AppContext> = {
|
|||||||
export function createAccessControlCli(context: AppContext) {
|
export function createAccessControlCli(context: AppContext) {
|
||||||
return createCli(rootCommand, {
|
return createCli(rootCommand, {
|
||||||
globalContext: context,
|
globalContext: context,
|
||||||
writer: (msg) => context.print(msg),
|
writer: (msg) => context.print({ text: msg }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ interface AccessConfig {
|
|||||||
watchInterval: number;
|
watchInterval: number;
|
||||||
noticeTimes: number;
|
noticeTimes: number;
|
||||||
detectRange: number;
|
detectRange: number;
|
||||||
|
isWelcome: boolean;
|
||||||
isWarn: boolean;
|
isWarn: boolean;
|
||||||
adminGroupConfig: UserGroupConfig;
|
adminGroupConfig: UserGroupConfig;
|
||||||
welcomeToastConfig: ToastConfig;
|
welcomeToastConfig: ToastConfig;
|
||||||
@@ -35,12 +36,13 @@ const defaultConfig: AccessConfig = {
|
|||||||
watchInterval: 10,
|
watchInterval: 10,
|
||||||
noticeTimes: 2,
|
noticeTimes: 2,
|
||||||
isWarn: false,
|
isWarn: false,
|
||||||
|
isWelcome: true,
|
||||||
adminGroupConfig: {
|
adminGroupConfig: {
|
||||||
groupName: "Admin",
|
groupName: "Admin",
|
||||||
groupUsers: ["Selcon"],
|
groupUsers: ["Selcon"],
|
||||||
isAllowed: true,
|
isAllowed: true,
|
||||||
isNotice: true,
|
isNotice: true,
|
||||||
isWelcome: true,
|
isWelcome: false,
|
||||||
},
|
},
|
||||||
usersGroups: [
|
usersGroups: [
|
||||||
{
|
{
|
||||||
@@ -50,6 +52,13 @@ const defaultConfig: AccessConfig = {
|
|||||||
isNotice: true,
|
isNotice: true,
|
||||||
isWelcome: false,
|
isWelcome: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
groupName: "TU",
|
||||||
|
groupUsers: [],
|
||||||
|
isAllowed: true,
|
||||||
|
isNotice: false,
|
||||||
|
isWelcome: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
groupName: "VIP",
|
groupName: "VIP",
|
||||||
groupUsers: [],
|
groupUsers: [],
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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 { 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 { ChatManager } from "@/lib/ChatManager";
|
||||||
@@ -15,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.Debug,
|
outputMinLevel: LogLevel.Info,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load Config
|
// Load Config
|
||||||
@@ -278,6 +277,8 @@ function mainLoop() {
|
|||||||
isNotice: false,
|
isNotice: false,
|
||||||
isWelcome: false,
|
isWelcome: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get user group config
|
||||||
for (const userGroupConfig of config.usersGroups) {
|
for (const userGroupConfig of config.usersGroups) {
|
||||||
if (userGroupConfig.groupUsers == undefined) continue;
|
if (userGroupConfig.groupUsers == undefined) continue;
|
||||||
if (!userGroupConfig.groupUsers.includes(player)) continue;
|
if (!userGroupConfig.groupUsers.includes(player)) continue;
|
||||||
@@ -286,21 +287,27 @@ function mainLoop() {
|
|||||||
logger.info(
|
logger.info(
|
||||||
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
`${groupConfig.groupName} ${player} appear at ${playerInfo?.x}, ${playerInfo?.y}, ${playerInfo?.z}`,
|
||||||
);
|
);
|
||||||
|
if (userGroupConfig.isWelcome)
|
||||||
|
sendMessage(config.welcomeToastConfig, player, {
|
||||||
|
playerName: player,
|
||||||
|
groupName: groupConfig.groupName,
|
||||||
|
info: playerInfo,
|
||||||
|
});
|
||||||
|
|
||||||
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.isWelcome)
|
||||||
|
sendMessage(config.welcomeToastConfig, player, {
|
||||||
|
playerName: player,
|
||||||
|
groupName: groupConfig.groupName,
|
||||||
|
info: playerInfo,
|
||||||
|
});
|
||||||
if (config.isWarn) sendWarn(player);
|
if (config.isWarn) sendWarn(player);
|
||||||
gWatchPlayersInfo = [
|
gWatchPlayersInfo = [
|
||||||
...gWatchPlayersInfo,
|
...gWatchPlayersInfo,
|
||||||
|
|||||||
@@ -323,6 +323,15 @@ const AccessControlTUI = () => {
|
|||||||
onChange: (checked) => setConfig("isWarn", checked),
|
onChange: (checked) => setConfig("isWarn", checked),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
div(
|
||||||
|
{ class: "flex flex-row" },
|
||||||
|
label({}, "Is Welcome:"),
|
||||||
|
input({
|
||||||
|
type: "checkbox",
|
||||||
|
checked: () => config().isWelcome ?? false,
|
||||||
|
onChange: (checked) => setConfig("isWelcome", checked),
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user