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.
This commit is contained in:
Jean Chevronnet 2026-07-29 16:13:24 +00:00
parent a15fb4f9a9
commit a5e58493d8
8 changed files with 426 additions and 21 deletions

17
src/bot/modules/mod.rs Normal file
View file

@ -0,0 +1,17 @@
//! The registry of feature modules. Add a feature by creating a file here that
//! implements [`super::module::Module`] and listing its constructor in [`all`].
pub mod builtins;
pub mod dice;
use super::module::Module;
/// Construct the default set of modules, in priority order (earlier modules win
/// command-name collisions). Each network's [`super::Bot`] gets its own fresh
/// set, so stateful modules keep independent per-network state.
pub fn all() -> Vec<Box<dyn Module>> {
vec![
Box::new(builtins::Builtins),
Box::new(dice::Dice::new()),
]
}