Add a module (plugin) system for bot features
Introduce a `Module` trait (src/bot/module.rs): a feature declares the commands it provides and turns each invocation into a `Vec<Action>`, without touching the connection. Modules are pure — context in, actions out — so they are unit-testable in isolation and avoid borrowing the `Bot` that owns them. Bot::new builds the module set and a command -> module route table; the `commands` dispatcher routes to the owning module and executes its actions, and `help` is auto-generated from every module's CommandSpec metadata. The existing ping/echo/hello move into a `builtins` module, and a stateful `dice` module (`roll [NdM]`, with its own dependency-free xorshift PRNG) is added as a worked example. Each network gets its own module set, so stateful modules keep independent per-network state with no locking. Adding a feature is now a new file in bot/modules/ plus one line in modules::all(). Tests: new tests/modules.rs drives modules through the trait with no network; end-to-end dispatch and auto-help verified against a fake server. No new deps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
eb3b8fec40
commit
c522e3aa3e
8 changed files with 426 additions and 21 deletions
41
README.md
41
README.md
|
|
@ -10,7 +10,7 @@ rustbot is a **library crate** (`src/lib.rs`) with a thin binary on top:
|
|||
| Path | Layer | Responsibility |
|
||||
|-----------------|----------------|-------------------------------------------------------------------|
|
||||
| `src/irc/` | protocol + I/O | `message` (parse IRCv3 wire format), `connection` (TCP/TLS transport), `encoding` (base64, server-time) |
|
||||
| `src/bot/` | behavior | `mod` (state + event loop), `caps` (IRCv3 cap negotiation + SASL), `commands` (message routing + command table) |
|
||||
| `src/bot/` | behavior | `mod` (state + loop), `caps` (IRCv3 + SASL), `commands` (routing + help), `module` (plugin trait), `modules/` (feature modules) |
|
||||
| `src/config.rs` | configuration | Layered `Config`; multi-network `[section]` parsing |
|
||||
| `src/lib.rs` | crate root | Ties the modules into the reusable `rustbot` library |
|
||||
| `src/main.rs` | binary | Thin per-network connect/reconnect supervisor |
|
||||
|
|
@ -18,7 +18,44 @@ rustbot is a **library crate** (`src/lib.rs`) with a thin binary on top:
|
|||
|
||||
The layers are deliberately separable: `irc` knows nothing about the bot, `bot`
|
||||
knows nothing about how the process is launched, and `main` only supervises.
|
||||
Adding a command is usually a one-file change in `src/bot/commands.rs`.
|
||||
Adding a feature is a new **module** — see [Modules](#modules).
|
||||
|
||||
## Modules
|
||||
|
||||
Features are **modules**: self-contained units behind the `Module` trait
|
||||
(`src/bot/module.rs`). A module declares the commands it provides and turns each
|
||||
invocation into `Action`s — it never touches the socket, so it stays pure and
|
||||
unit-testable:
|
||||
|
||||
```rust
|
||||
pub trait Module {
|
||||
fn name(&self) -> &'static str;
|
||||
fn commands(&self) -> &'static [CommandSpec];
|
||||
fn on_command(&mut self, cmd: &Command) -> Vec<Action>;
|
||||
}
|
||||
```
|
||||
|
||||
At startup the bot builds a `command → module` route table and auto-generates
|
||||
`help` from every module's `CommandSpec`s. Each network gets its own fresh set
|
||||
of modules, so stateful ones (like the dice roller's PRNG) keep per-network
|
||||
state with no locking.
|
||||
|
||||
**To add a module**, create `src/bot/modules/<name>.rs` implementing `Module`,
|
||||
then add one line to `all()` in `src/bot/modules/mod.rs`:
|
||||
|
||||
```rust
|
||||
pub fn all() -> Vec<Box<dyn Module>> {
|
||||
vec![
|
||||
Box::new(builtins::Builtins),
|
||||
Box::new(dice::Dice::new()),
|
||||
Box::new(myfeature::MyFeature::new()), // <- your module
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Shipped modules: **`builtins`** (`ping`, `echo`, `hello`) and **`dice`**
|
||||
(`roll [NdM]`). Module tests live in `tests/modules.rs` — construct a module and
|
||||
assert on the `Action`s it returns, no network required.
|
||||
|
||||
## Build & run
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue