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:
parent
a15fb4f9a9
commit
a5e58493d8
8 changed files with 426 additions and 21 deletions
33
src/bot/modules/builtins.rs
Normal file
33
src/bot/modules/builtins.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
//! The built-in commands: `ping`, `echo`, `hello`. (`help` is provided by the
|
||||
//! dispatcher itself, since it aggregates every module's commands.)
|
||||
|
||||
use crate::bot::module::{Action, Command, CommandSpec, Module};
|
||||
|
||||
/// Core commands that ship with the bot.
|
||||
pub struct Builtins;
|
||||
|
||||
const COMMANDS: &[CommandSpec] = &[
|
||||
CommandSpec { name: "ping", usage: "ping", about: "reply with 'pong'" },
|
||||
CommandSpec { name: "echo", usage: "echo <text>", about: "echo the text back" },
|
||||
CommandSpec { name: "hello", usage: "hello", about: "greet the sender" },
|
||||
];
|
||||
|
||||
impl Module for Builtins {
|
||||
fn name(&self) -> &'static str {
|
||||
"builtins"
|
||||
}
|
||||
|
||||
fn commands(&self) -> &'static [CommandSpec] {
|
||||
COMMANDS
|
||||
}
|
||||
|
||||
fn on_command(&mut self, cmd: &Command) -> Vec<Action> {
|
||||
let reply = match cmd.name {
|
||||
"ping" => "pong".to_string(),
|
||||
"echo" => cmd.args.join(" "),
|
||||
"hello" => format!("hello, {}!", cmd.sender),
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
vec![Action::Reply(reply)]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue