4.3 KiB
The Module trait
A module hooks lifecycle events. Implement crate::module::Module on a struct and
register it in default_modules(). Every method has a default, so implement only
the hooks you need.
pub trait Module: Send {
fn name(&self) -> &'static str;
// pre-hooks — fired inline, can Deny the action
fn on_user_register(&mut self, srv: &mut Server, uid: Uid) -> ModResult { ModResult::Passthru }
fn on_pre_command(&mut self, srv: &mut Server, uid: Uid, cmd: &str, params: &[String]) -> ModResult { ModResult::Passthru }
fn on_pre_message(&mut self, srv: &mut Server, uid: Uid, target: &str, text: &str) -> ModResult { ModResult::Passthru }
// notify-hooks — informational, fired after the fact
fn on_user_connect(&mut self, srv: &mut Server, uid: Uid) {}
fn on_post_command(&mut self, srv: &mut Server, uid: Uid, cmd: &str) {}
fn on_join(&mut self, srv: &mut Server, uid: Uid, chan: &str) {}
fn on_part(&mut self, srv: &mut Server, uid: Uid, chan: &str, reason: &str) {}
fn on_user_quit(&mut self, srv: &mut Server, uid: Uid, reason: &str) {}
fn on_tick(&mut self, srv: &mut Server) {}
}
Note the receiver is &mut self: unlike commands and modes (which are stateless
&self handlers), a module instance can hold its own fields. In practice, prefer
the Extensible typemap for per-user / per-channel
state so it lives and dies with its owner; use self fields only for
module-global state.
Pre-hooks (can deny)
Pre-hooks run inline, before the action they gate, and return a ModResult:
pub enum ModResult {
Passthru, // no opinion — let other modules and the core decide
Allow, // force-allow: skip the remaining checks
Deny, // block the action
}
| Hook | Fires | Deny effect |
|---|---|---|
on_user_register |
last gate before a client finishes registration | refuses the connection |
on_pre_command |
before any command runs | swallows the command silently |
on_pre_message |
before a PRIVMSG / NOTICE is delivered |
drops the message |
Return Deny to block, Allow to force it through (bypassing other checks), or
Passthru to abstain. When you Deny, send the user an explanation yourself
(e.g. srv.numeric(...) or srv.fail(...)), since the core just stops.
fn on_pre_message(&mut self, srv: &mut Server, uid: Uid, target: &str, text: &str) -> ModResult {
if text.contains("badword") && !srv.is_oper(uid) {
srv.numeric(uid, 404, &format!("{target} :Message blocked"));
return ModResult::Deny;
}
ModResult::Passthru
}
Notify-hooks (informational)
Notify-hooks run after the event, drained from a queue once the triggering
command finishes. They can't block, but they get &mut Server, so they can act —
send lines, force a join, update state.
| Hook | Fires when |
|---|---|
on_user_connect |
a client has fully registered |
on_post_command |
after a command completes |
on_join |
a user joined a channel |
on_part |
a user left a channel |
on_user_quit |
a user is disconnecting (still exists during the call) |
on_tick |
the background timer, every TICK_SECS |
Because notify-hooks fire from a queue, a hook can itself cause more events (e.g. force a join) without re-entering the module list — no surprises.
Timed work
Use on_tick for periodic jobs (expiring entries, saving state, scoring). It runs
on the core thread, so keep it cheap; for a big write, hand it to
disk_write rather than blocking.
fn on_tick(&mut self, srv: &mut Server) {
let store = srv.ext.get_or_insert_with::<MyStore>(MyStore::default);
store.expire(crate::server::now());
// persist off-core so a slow disk can't stall the core:
srv.disk_write(format!("{}.mystore", srv.conf_path), store.serialize());
}
Registering
Add your module to src/modules/mod.rs:
pub mod mymod; // declare the file
// …in default_modules():
Box::new(mymod::MyMod), // add to the vec
Order in the vec is the order pre-hooks are consulted; the first Deny (or
Allow) wins.
See commands if your module also adds commands, and
the Server API for everything you can call from a hook.